From ec7a2476740f31ae02ca438e73b7767902dab64b Mon Sep 17 00:00:00 2001 From: "Taewoo K." Date: Fri, 9 Aug 2024 18:48:31 -0400 Subject: [PATCH 001/229] feat: Add atlassian statuspage (#91769) * add atlassian statuspage * fix test --- docs/sources/introduction/grafana-enterprise.md | 1 + .../app/features/datasources/state/buildCategories.test.ts | 2 +- public/app/features/datasources/state/buildCategories.ts | 6 ++++++ public/img/plugins/atlassian-statuspage.svg | 1 + 4 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 public/img/plugins/atlassian-statuspage.svg diff --git a/docs/sources/introduction/grafana-enterprise.md b/docs/sources/introduction/grafana-enterprise.md index ddddfc443b4..76b936c6dd5 100644 --- a/docs/sources/introduction/grafana-enterprise.md +++ b/docs/sources/introduction/grafana-enterprise.md @@ -76,6 +76,7 @@ With a Grafana Enterprise license, you also get access to premium data sources, - [Adobe Analytics](/grafana/plugins/grafana-adobeanalytics-datasource) - [AppDynamics](/grafana/plugins/dlopes7-appdynamics-datasource) +- [Atlassian Statuspage](/grafana/plugins/grafana-atlassianstatuspage-datasource) - [Azure CosmosDB](/grafana/plugins/grafana-azurecosmosdb-datasource) - [Azure Devops](/grafana/plugins/grafana-azuredevops-datasource) - [Catchpoint](/grafana/plugins/grafana-catchpoint-datasource) diff --git a/public/app/features/datasources/state/buildCategories.test.ts b/public/app/features/datasources/state/buildCategories.test.ts index 4853f56cca1..31d39b1a46d 100644 --- a/public/app/features/datasources/state/buildCategories.test.ts +++ b/public/app/features/datasources/state/buildCategories.test.ts @@ -53,7 +53,7 @@ describe('buildCategories', () => { it('should add enterprise phantom plugins', () => { const enterprisePluginsCategory = categories[3]; expect(enterprisePluginsCategory.title).toBe('Enterprise plugins'); - expect(enterprisePluginsCategory.plugins.length).toBe(26); + expect(enterprisePluginsCategory.plugins.length).toBe(27); expect(enterprisePluginsCategory.plugins[0].name).toBe('Adobe Analytics'); expect(enterprisePluginsCategory.plugins[enterprisePluginsCategory.plugins.length - 1].name).toBe('Wavefront'); }); diff --git a/public/app/features/datasources/state/buildCategories.ts b/public/app/features/datasources/state/buildCategories.ts index dc56f3f18bb..3dd31666066 100644 --- a/public/app/features/datasources/state/buildCategories.ts +++ b/public/app/features/datasources/state/buildCategories.ts @@ -251,6 +251,12 @@ function getEnterprisePhantomPlugins(): DataSourcePluginMeta[] { name: 'Drone', imgUrl: 'public/img/plugins/drone.svg', }), + getPhantomPlugin({ + id: 'grafana-atlassianstatuspage-datasource', + description: 'Atlassian Statuspage datasource', + name: 'Atlassian Statuspage', + imgUrl: 'public/img/plugins/atlassian-statuspage.svg', + }), ]; } diff --git a/public/img/plugins/atlassian-statuspage.svg b/public/img/plugins/atlassian-statuspage.svg new file mode 100644 index 00000000000..8cd0b6c3d96 --- /dev/null +++ b/public/img/plugins/atlassian-statuspage.svg @@ -0,0 +1 @@ + \ No newline at end of file From 8bb548e17b4b4de66c3b9449501c0eb4f02a3869 Mon Sep 17 00:00:00 2001 From: Jmdane <70574656+jmdane@users.noreply.github.com> Date: Sat, 10 Aug 2024 01:57:26 +0200 Subject: [PATCH 002/229] Transformations: Add 'transpose' transform (#88963) Co-authored-by: Leon Sorokin --- .../transform-data/index.md | 5 + .../src/transformations/transformers.ts | 2 + .../src/transformations/transformers/ids.ts | 1 + .../transformers/transpose.test.ts | 241 ++++++++++++++++++ .../transformations/transformers/transpose.ts | 105 ++++++++ .../app/features/transformers/docs/content.ts | 9 + .../editors/TransposeTransformerEditor.tsx | 45 ++++ .../transformers/standardTransformers.ts | 2 + public/img/transformations/dark/transpose.svg | 1 + .../img/transformations/light/transpose.svg | 1 + 10 files changed, 412 insertions(+) create mode 100644 packages/grafana-data/src/transformations/transformers/transpose.test.ts create mode 100644 packages/grafana-data/src/transformations/transformers/transpose.ts create mode 100644 public/app/features/transformers/editors/TransposeTransformerEditor.tsx create mode 100644 public/img/transformations/dark/transpose.svg create mode 100644 public/img/transformations/light/transpose.svg diff --git a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md b/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md index 7ccfe7576a4..e94c3b32c0f 100644 --- a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md +++ b/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md @@ -1423,6 +1423,11 @@ For each generated **Trend** field value, a calculation function can be selected > **Note:** This transformation is available in Grafana 9.5+ as an opt-in beta feature. Modify the Grafana [configuration file][] to use it. +### Transpose + +Use this transformation to pivot the data frame, converting rows into columns and columns into rows. This transformation is particularly useful when you want to switch the orientation of your data to better suit your visualization needs. +If you have multiple types it will default to string type. + ### Regression analysis Use this transformation to create a new data frame containing values predicted by a statistical model. This is useful for finding a trend in chaotic data. It works by fitting a mathematical function to the data, using either linear or polynomial regression. The data frame can then be used in a visualization to display a trendline. diff --git a/packages/grafana-data/src/transformations/transformers.ts b/packages/grafana-data/src/transformations/transformers.ts index 3344a7fb450..05606a50a37 100644 --- a/packages/grafana-data/src/transformations/transformers.ts +++ b/packages/grafana-data/src/transformations/transformers.ts @@ -24,6 +24,7 @@ import { renameFieldsTransformer } from './transformers/rename'; import { renameByRegexTransformer } from './transformers/renameByRegex'; import { seriesToRowsTransformer } from './transformers/seriesToRows'; import { sortByTransformer } from './transformers/sortBy'; +import { transposeTransformer } from './transformers/transpose'; export const standardTransformers = { noopTransformer, @@ -55,4 +56,5 @@ export const standardTransformers = { groupingToMatrixTransformer, limitTransformer, groupToNestedTable, + transposeTransformer, }; diff --git a/packages/grafana-data/src/transformations/transformers/ids.ts b/packages/grafana-data/src/transformations/transformers/ids.ts index 06ec0cd6277..d429f298743 100644 --- a/packages/grafana-data/src/transformations/transformers/ids.ts +++ b/packages/grafana-data/src/transformations/transformers/ids.ts @@ -37,6 +37,7 @@ export enum DataTransformerID { limit = 'limit', partitionByValues = 'partitionByValues', timeSeriesTable = 'timeSeriesTable', + transpose = 'transpose', formatTime = 'formatTime', formatString = 'formatString', regression = 'regression', diff --git a/packages/grafana-data/src/transformations/transformers/transpose.test.ts b/packages/grafana-data/src/transformations/transformers/transpose.test.ts new file mode 100644 index 00000000000..e522b93e885 --- /dev/null +++ b/packages/grafana-data/src/transformations/transformers/transpose.test.ts @@ -0,0 +1,241 @@ +import { DataTransformerConfig } from '@grafana/schema'; + +import { toDataFrame } from '../../dataframe/processDataFrame'; +import { FieldType } from '../../types/dataFrame'; +import { mockTransformationsRegistry } from '../../utils/tests/mockTransformationsRegistry'; +import { transformDataFrame } from '../transformDataFrame'; + +import { DataTransformerID } from './ids'; +import { transposeTransformer, TransposeTransformerOptions } from './transpose'; + +describe('Transpose transformer', () => { + beforeAll(() => { + mockTransformationsRegistry([transposeTransformer]); + }); + + it('should transpose full numeric values and keep numeric type', async () => { + const cfgA: DataTransformerConfig = { + id: DataTransformerID.transpose, + options: {}, + }; + const seriesA = toDataFrame({ + name: 'A', + fields: [ + { name: 'env', type: FieldType.string, values: ['dev', 'prod', 'staging', 'release', 'beta'] }, + { name: 'january', type: FieldType.number, values: [11, 12, 13, 14, 15] }, + { name: 'february', type: FieldType.number, values: [6, 7, 8, 9, 10] }, + { name: 'march', type: FieldType.number, values: [1, 2, 3, 4, 5] }, + ], + }); + await expect(transformDataFrame([cfgA], [seriesA])).toEmitValuesWith((received) => { + const result = received[0]; + expect(result[0].fields).toEqual([ + { + name: 'Field', + type: FieldType.string, + values: ['january', 'february', 'march'], + config: {}, + }, + { + name: 'Value', + labels: { env: 'dev' }, + type: FieldType.number, + values: [11, 6, 1], + config: {}, + }, + { + name: 'Value', + labels: { env: 'prod' }, + type: FieldType.number, + values: [12, 7, 2], + config: {}, + }, + { + name: 'Value', + labels: { env: 'staging' }, + type: FieldType.number, + values: [13, 8, 3], + config: {}, + }, + { + name: 'Value', + labels: { env: 'release' }, + type: FieldType.number, + values: [14, 9, 4], + config: {}, + }, + { + name: 'Value', + labels: { env: 'beta' }, + type: FieldType.number, + values: [15, 10, 5], + config: {}, + }, + ]); + }); + }); + + it('should transpose and use string field type', async () => { + const cfgB: DataTransformerConfig = { + id: DataTransformerID.transpose, + options: {}, + }; + const seriesB = toDataFrame({ + name: 'B', + fields: [ + { name: 'env', type: FieldType.string, values: ['dev', 'prod', 'staging', 'release', 'beta'] }, + { name: 'january', type: FieldType.number, values: [11, 12, 13, 14, 15] }, + { name: 'february', type: FieldType.number, values: [6, 7, 8, 9, 10] }, + { name: 'type', type: FieldType.string, values: ['metricA', 'metricB', 'metricC', 'metricD', 'metricE'] }, + ], + }); + await expect(transformDataFrame([cfgB], [seriesB])).toEmitValuesWith((received) => { + const result = received[0]; + expect(result[0].fields).toEqual([ + { + name: 'Field', + type: FieldType.string, + values: ['january', 'february', 'type'], + config: {}, + }, + { + name: 'Value', + labels: { env: 'dev' }, + type: FieldType.string, + values: ['11', '6', 'metricA'], + config: {}, + }, + { + name: 'Value', + labels: { env: 'prod' }, + type: FieldType.string, + values: ['12', '7', 'metricB'], + config: {}, + }, + { + name: 'Value', + labels: { env: 'staging' }, + type: FieldType.string, + values: ['13', '8', 'metricC'], + config: {}, + }, + { + name: 'Value', + labels: { env: 'release' }, + type: FieldType.string, + values: ['14', '9', 'metricD'], + config: {}, + }, + { + name: 'Value', + labels: { env: 'beta' }, + type: FieldType.string, + values: ['15', '10', 'metricE'], + config: {}, + }, + ]); + }); + }); + + it('should transpose and keep number types and add new headers', async () => { + const cfgC: DataTransformerConfig = { + id: DataTransformerID.transpose, + options: { + firstFieldName: 'NewField', + }, + }; + const seriesC = toDataFrame({ + name: 'C', + fields: [ + { name: 'A', type: FieldType.number, values: [1, 5] }, + { name: 'B', type: FieldType.number, values: [2, 6] }, + { name: 'C', type: FieldType.number, values: [3, 7] }, + { name: 'D', type: FieldType.number, values: [4, 8] }, + ], + }); + await expect(transformDataFrame([cfgC], [seriesC])).toEmitValuesWith((received) => { + const result = received[0]; + expect(result[0].fields).toEqual([ + { + name: 'NewField', + type: FieldType.string, + values: ['A', 'B', 'C', 'D'], + config: {}, + }, + { + name: 'Value', + labels: { row: 1 }, + type: FieldType.number, + values: [1, 2, 3, 4], + config: {}, + }, + { + name: 'Value', + labels: { row: 2 }, + type: FieldType.number, + values: [5, 6, 7, 8], + config: {}, + }, + ]); + }); + }); + + it('should transpose and handle different types and rename first element', async () => { + const cfgD: DataTransformerConfig = { + id: DataTransformerID.transpose, + options: { + firstFieldName: 'Field1', + }, + }; + const seriesD = toDataFrame({ + name: 'D', + fields: [ + { + name: 'time', + type: FieldType.time, + values: ['2024-06-10 08:30:00', '2024-06-10 08:31:00', '2024-06-10 08:32:00', '2024-06-10 08:33:00'], + }, + { name: 'value', type: FieldType.number, values: [1, 2, 3, 4] }, + ], + }); + await expect(transformDataFrame([cfgD], [seriesD])).toEmitValuesWith((received) => { + const result = received[0]; + expect(result[0].fields).toEqual([ + { + name: 'Field1', + type: FieldType.string, + values: ['value'], + config: {}, + }, + { + name: 'Value', + labels: { time: '2024-06-10 08:30:00' }, + type: FieldType.number, + values: [1], + config: {}, + }, + { + name: 'Value', + labels: { time: '2024-06-10 08:31:00' }, + type: FieldType.number, + values: [2], + config: {}, + }, + { + name: 'Value', + labels: { time: '2024-06-10 08:32:00' }, + type: FieldType.number, + values: [3], + config: {}, + }, + { + name: 'Value', + labels: { time: '2024-06-10 08:33:00' }, + type: FieldType.number, + values: [4], + config: {}, + }, + ]); + }); + }); +}); diff --git a/packages/grafana-data/src/transformations/transformers/transpose.ts b/packages/grafana-data/src/transformations/transformers/transpose.ts new file mode 100644 index 00000000000..b4e04c9d316 --- /dev/null +++ b/packages/grafana-data/src/transformations/transformers/transpose.ts @@ -0,0 +1,105 @@ +import { map } from 'rxjs/operators'; + +import { DataFrame, Field, FieldType } from '../../types/dataFrame'; +import { DataTransformerInfo } from '../../types/transformations'; + +import { DataTransformerID } from './ids'; + +export interface TransposeTransformerOptions { + firstFieldName?: string; + restFieldsName?: string; +} + +export const transposeTransformer: DataTransformerInfo = { + id: DataTransformerID.transpose, + name: 'Transpose', + description: 'Transpose the data frame', + defaultOptions: {}, + + operator: (options) => (source) => + source.pipe( + map((data) => { + if (data.length === 0) { + return data; + } + return transposeDataFrame(options, data); + }) + ), +}; + +function transposeDataFrame(options: TransposeTransformerOptions, data: DataFrame[]): DataFrame[] { + return data.map((frame) => { + const firstField = frame.fields[0]; + const firstName = !options.firstFieldName ? 'Field' : options.firstFieldName; + const restName = !options.restFieldsName ? 'Value' : options.restFieldsName; + const useFirstFieldAsHeaders = + firstField.type === FieldType.string || firstField.type === FieldType.time || firstField.type === FieldType.enum; + const headers = useFirstFieldAsHeaders + ? [firstName, ...fieldValuesAsStrings(firstField, firstField.values)] + : [firstName, ...firstField.values.map((_, i) => restName)]; + const rows = useFirstFieldAsHeaders + ? frame.fields.map((field) => field.name).slice(1) + : frame.fields.map((field) => field.name); + const fieldType = determineFieldType( + useFirstFieldAsHeaders + ? frame.fields.map((field) => field.type).slice(1) + : frame.fields.map((field) => field.type) + ); + + const newFields = headers.map((fieldName, index) => { + if (index === 0) { + return { + name: firstName, + type: FieldType.string, + config: {}, + values: rows, + }; + } + + const values = frame.fields.map((field) => { + if (fieldType === FieldType.string) { + return fieldValuesAsStrings(field, [field.values[index - 1]])[0]; + } + return field.values[index - 1]; + }); + + const labelName = useFirstFieldAsHeaders ? firstField.name : 'row'; + const labelValue = useFirstFieldAsHeaders ? fieldName : index; + + return { + name: useFirstFieldAsHeaders ? restName : fieldName, + labels: { + [labelName]: labelValue, + }, + type: fieldType, + config: {}, + values: useFirstFieldAsHeaders ? values.slice(1) : values, + }; + }); + return { + ...frame, + fields: newFields, + length: Math.max(...newFields.map((field) => field.values.length)), + }; + }); +} + +function determineFieldType(fieldTypes: FieldType[]): FieldType { + const uniqueFieldTypes = new Set(fieldTypes); + return uniqueFieldTypes.size === 1 ? [...uniqueFieldTypes][0] : FieldType.string; +} + +function fieldValuesAsStrings(field: Field, values: unknown[]) { + switch (field.type) { + case FieldType.time: + case FieldType.number: + case FieldType.boolean: + case FieldType.string: + return values.map((v) => `${v}`); + case FieldType.enum: + // @ts-ignore + return values.map((v) => field.config.type!.enum!.text![v]); + default: + return values.map((v) => JSON.stringify(v)); + } +} diff --git a/public/app/features/transformers/docs/content.ts b/public/app/features/transformers/docs/content.ts index e9442e582cc..490df92529f 100644 --- a/public/app/features/transformers/docs/content.ts +++ b/public/app/features/transformers/docs/content.ts @@ -1520,6 +1520,15 @@ ${buildImageContent( }, ], }, + transpose: { + name: 'Transpose', + getHelperDocs: function () { + return ` +Use this transformation to pivot the data frame, converting rows into columns and columns into rows. This transformation is particularly useful when you want to switch the orientation of your data to better suit your visualization needs. +If you have multiple types it will default to string type. + `; + }, + }, regression: { name: 'Regression analysis', getHelperDocs: function (imageRenderType: ImageRenderType = ImageRenderType.ShortcodeFigure) { diff --git a/public/app/features/transformers/editors/TransposeTransformerEditor.tsx b/public/app/features/transformers/editors/TransposeTransformerEditor.tsx new file mode 100644 index 00000000000..4190d385e48 --- /dev/null +++ b/public/app/features/transformers/editors/TransposeTransformerEditor.tsx @@ -0,0 +1,45 @@ +import { + DataTransformerID, + standardTransformers, + TransformerRegistryItem, + TransformerUIProps, + TransformerCategory, +} from '@grafana/data'; +import { TransposeTransformerOptions } from '@grafana/data/src/transformations/transformers/transpose'; +import { InlineField, InlineFieldRow, Input } from '@grafana/ui'; + +export const TransposeTransfomerEditor = ({ options, onChange }: TransformerUIProps) => { + return ( + <> + + + onChange({ ...options, firstFieldName: e.currentTarget.value })} + width={25} + /> + + + + + onChange({ ...options, restFieldsName: e.currentTarget.value })} + width={25} + /> + + + + ); +}; + +export const transposeTransformerRegistryItem: TransformerRegistryItem = { + id: DataTransformerID.transpose, + editor: TransposeTransfomerEditor, + transformation: standardTransformers.transposeTransformer, + name: standardTransformers.transposeTransformer.name, + description: standardTransformers.transposeTransformer.description, + categories: new Set([TransformerCategory.Reformat]), +}; diff --git a/public/app/features/transformers/standardTransformers.ts b/public/app/features/transformers/standardTransformers.ts index 04f45d20e21..2fbc9fce5db 100644 --- a/public/app/features/transformers/standardTransformers.ts +++ b/public/app/features/transformers/standardTransformers.ts @@ -24,6 +24,7 @@ import { reduceTransformRegistryItem } from './editors/ReduceTransformerEditor'; import { renameByRegexTransformRegistryItem } from './editors/RenameByRegexTransformer'; import { seriesToRowsTransformerRegistryItem } from './editors/SeriesToRowsTransformerEditor'; import { sortByTransformRegistryItem } from './editors/SortByTransformerEditor'; +import { transposeTransformerRegistryItem } from './editors/TransposeTransformerEditor'; import { extractFieldsTransformRegistryItem } from './extractFields/ExtractFieldsTransformerEditor'; import { joinByLabelsTransformRegistryItem } from './joinByLabels/JoinByLabelsTransformerEditor'; import { fieldLookupTransformRegistryItem } from './lookupGazetteer/FieldLookupTransformerEditor'; @@ -68,5 +69,6 @@ export const getStandardTransformers = (): Array> = ...(config.featureToggles.groupToNestedTableTransformation ? [groupToNestedTableTransformRegistryItem] : []), formatTimeTransformerRegistryItem, timeSeriesTableTransformRegistryItem, + transposeTransformerRegistryItem, ]; }; diff --git a/public/img/transformations/dark/transpose.svg b/public/img/transformations/dark/transpose.svg new file mode 100644 index 00000000000..ba9b8eb03fc --- /dev/null +++ b/public/img/transformations/dark/transpose.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/img/transformations/light/transpose.svg b/public/img/transformations/light/transpose.svg new file mode 100644 index 00000000000..3997edb0248 --- /dev/null +++ b/public/img/transformations/light/transpose.svg @@ -0,0 +1 @@ + \ No newline at end of file From 75f0b3a228efe768d844244367e11d94e7e51dd2 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Sat, 10 Aug 2024 20:32:57 -0400 Subject: [PATCH 003/229] K8s: Test for changes to aggregator's postStartHooks (#91771) --- .../apiserver/aggregator/aggregator_test.go | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 pkg/services/apiserver/aggregator/aggregator_test.go diff --git a/pkg/services/apiserver/aggregator/aggregator_test.go b/pkg/services/apiserver/aggregator/aggregator_test.go new file mode 100644 index 00000000000..f1a97039d61 --- /dev/null +++ b/pkg/services/apiserver/aggregator/aggregator_test.go @@ -0,0 +1,70 @@ +package aggregator_test + +import ( + "sort" + "testing" + "time" + + "github.com/grafana/grafana/pkg/storage/unified/apistore" + "github.com/stretchr/testify/require" + openapinamer "k8s.io/apiserver/pkg/endpoints/openapi" + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/apiserver/pkg/storage/storagebackend" + utilversion "k8s.io/apiserver/pkg/util/version" + "k8s.io/client-go/informers" + "k8s.io/client-go/kubernetes/fake" + clientrest "k8s.io/client-go/rest" + "k8s.io/kube-aggregator/pkg/apiserver" + aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" + aggregatoropenapi "k8s.io/kube-aggregator/pkg/generated/openapi" +) + +// TestAggregatorPostStartHooks tests that the kube-aggregator server has the expected default post start hooks enabled. +func TestAggregatorPostStartHooks(t *testing.T) { + cfg := apiserver.Config{ + GenericConfig: genericapiserver.NewRecommendedConfig(aggregatorscheme.Codecs), + ExtraConfig: apiserver.ExtraConfig{}, + } + + cfg.GenericConfig.ExternalAddress = "127.0.0.1:6443" + cfg.GenericConfig.EffectiveVersion = utilversion.DefaultBuildEffectiveVersion() + cfg.GenericConfig.LoopbackClientConfig = &clientrest.Config{} + cfg.GenericConfig.MergedResourceConfig = apiserver.DefaultAPIResourceConfigSource() + + // Add OpenAPI config, which depends on builders + namer := openapinamer.NewDefinitionNamer(aggregatorscheme.Scheme) + cfg.GenericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(aggregatoropenapi.GetOpenAPIDefinitions, namer) + cfg.GenericConfig.OpenAPIV3Config.Info.Title = "Kubernetes" + cfg.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(aggregatoropenapi.GetOpenAPIDefinitions, namer) + cfg.GenericConfig.OpenAPIConfig.Info.Title = "Kubernetes" + cfg.GenericConfig.SkipOpenAPIInstallation = true + cfg.GenericConfig.SharedInformerFactory = informers.NewSharedInformerFactory(fake.NewSimpleClientset(), 10*time.Minute) + + // override the RESTOptionsGetter to use the in memory storage options + restOptionsGetter, err := apistore.NewRESTOptionsGetterMemory(*storagebackend.NewDefaultConfig("memory", nil)) + require.NoError(t, err) + cfg.GenericConfig.RESTOptionsGetter = restOptionsGetter + + complete := cfg.Complete() + + server, err := complete.NewWithDelegate(genericapiserver.NewEmptyDelegate()) + require.NoError(t, err) + + actual := make([]string, 0, len(server.GenericAPIServer.PostStartHooks())) + for k := range server.GenericAPIServer.PostStartHooks() { + actual = append(actual, k) + } + sort.Strings(actual) + expected := []string{ + "apiservice-discovery-controller", + "generic-apiserver-start-informers", + "max-in-flight-filter", + "storage-object-count-tracker-hook", + "start-kube-aggregator-informers", + "apiservice-status-local-available-controller", + "apiservice-status-remote-available-controller", + "apiservice-registration-controller", + } + sort.Strings(expected) + require.Equal(t, expected, actual) +} From b8ebc5d4635b6a397ed9c02ca3e6edf5df42cdfc Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Sun, 11 Aug 2024 23:48:04 +0200 Subject: [PATCH 004/229] Chore: Update lezer package of prometheus frontend (#91252) * update lezer package * Use UnquotedLabelMatcher instead of LabelMatcher * Use UnquotedLabelMatcher instead of LabelMatcher in parsing.ts --- package.json | 1 - packages/grafana-prometheus/package.json | 4 ++-- .../monaco-completion-provider/situation.ts | 16 +++++++------- .../src/querybuilder/parsing.test.ts | 2 +- .../src/querybuilder/parsing.ts | 4 ++-- yarn.lock | 21 +++++++++---------- 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/package.json b/package.json index 80bdf7108c1..a83bd3fbeee 100644 --- a/package.json +++ b/package.json @@ -282,7 +282,6 @@ "@opentelemetry/exporter-collector": "0.25.0", "@opentelemetry/semantic-conventions": "1.25.1", "@popperjs/core": "2.11.8", - "@prometheus-io/lezer-promql": "^0.52.0", "@react-aria/dialog": "3.5.16", "@react-aria/focus": "3.18.1", "@react-aria/overlays": "3.23.1", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 5d83a16eac4..849befcdb9c 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -48,8 +48,8 @@ "@leeoniya/ufuzzy": "1.0.14", "@lezer/common": "1.2.1", "@lezer/highlight": "1.2.0", - "@lezer/lr": "1.4.0", - "@prometheus-io/lezer-promql": "0.52.1", + "@lezer/lr": "1.4.2", + "@prometheus-io/lezer-promql": "0.53.1", "@reduxjs/toolkit": "2.2.7", "d3": "7.9.0", "date-fns": "3.6.0", diff --git a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts index decb3c2c9ac..0bc0d18cdd5 100644 --- a/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts +++ b/packages/grafana-prometheus/src/components/monaco-query-field/monaco-completion-provider/situation.ts @@ -9,7 +9,6 @@ import { FunctionCallBody, GroupingLabels, Identifier, - LabelMatcher, LabelMatchers, LabelName, MatchOp, @@ -19,6 +18,7 @@ import { parser, PromQL, StringLiteral, + UnquotedLabelMatcher, VectorSelector, } from '@prometheus-io/lezer-promql'; @@ -33,7 +33,7 @@ type NodeTypeId = | typeof FunctionCallBody | typeof GroupingLabels | typeof Identifier - | typeof LabelMatcher + | typeof UnquotedLabelMatcher | typeof LabelMatchers | typeof LabelName | typeof PromQL @@ -178,7 +178,7 @@ const RESOLVERS: Resolver[] = [ fun: resolveInFunction, }, { - path: [StringLiteral, LabelMatcher], + path: [StringLiteral, UnquotedLabelMatcher], fun: resolveLabelMatcher, }, { @@ -186,7 +186,7 @@ const RESOLVERS: Resolver[] = [ fun: resolveTopLevel, }, { - path: [ERROR_NODE_NAME, LabelMatcher], + path: [ERROR_NODE_NAME, UnquotedLabelMatcher], fun: resolveLabelMatcher, }, { @@ -216,7 +216,7 @@ function getLabelOp(opNode: SyntaxNode): LabelOperator | null { } function getLabel(labelMatcherNode: SyntaxNode, text: string): Label | null { - if (labelMatcherNode.type.id !== LabelMatcher) { + if (labelMatcherNode.type.id !== UnquotedLabelMatcher) { return null; } @@ -253,7 +253,7 @@ function getLabels(labelMatchersNode: SyntaxNode, text: string): Label[] { return []; } - const labelNodes = labelMatchersNode.getChildren(LabelMatcher); + const labelNodes = labelMatchersNode.getChildren(UnquotedLabelMatcher); return labelNodes.map((ln) => getLabel(ln, text)).filter(notEmpty); } @@ -317,7 +317,7 @@ function resolveLabelMatcher(node: SyntaxNode, text: string, pos: number): Situa // - or an error node (like in `{job=^}`) const inStringNode = !node.type.isError; - const parent = walk(node, [['parent', LabelMatcher]]); + const parent = walk(node, [['parent', UnquotedLabelMatcher]]); if (parent === null) { return null; } @@ -387,7 +387,7 @@ function resolveDurations(node: SyntaxNode, text: string, pos: number): Situatio function resolveLabelKeysWithEquals(node: SyntaxNode, text: string, pos: number): Situation | null { // next false positive: // `something{a="1"^}` - const child = walk(node, [['firstChild', LabelMatcher]]); + const child = walk(node, [['firstChild', UnquotedLabelMatcher]]); if (child !== null) { // means the label-matching part contains at least one label already. // diff --git a/packages/grafana-prometheus/src/querybuilder/parsing.test.ts b/packages/grafana-prometheus/src/querybuilder/parsing.test.ts index 406f9fd8871..ce2aaede3ac 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsing.test.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsing.test.ts @@ -514,7 +514,7 @@ describe('buildVisualQueryFromString', () => { text: 'afe}', from: 14, to: 18, - parentType: 'LabelMatcher', + parentType: 'UnquotedLabelMatcher', }, ], query: { diff --git a/packages/grafana-prometheus/src/querybuilder/parsing.ts b/packages/grafana-prometheus/src/querybuilder/parsing.ts index 95a12032051..ecdf9044d7e 100644 --- a/packages/grafana-prometheus/src/querybuilder/parsing.ts +++ b/packages/grafana-prometheus/src/querybuilder/parsing.ts @@ -11,7 +11,6 @@ import { FunctionIdentifier, GroupingLabels, Identifier, - LabelMatcher, LabelName, MatchingModifierClause, MatchOp, @@ -20,6 +19,7 @@ import { ParenExpr, parser, StringLiteral, + UnquotedLabelMatcher, VectorSelector, Without, } from '@prometheus-io/lezer-promql'; @@ -145,7 +145,7 @@ export function handleExpression(expr: string, node: SyntaxNode, context: Contex break; } - case LabelMatcher: { + case UnquotedLabelMatcher: { // Same as MetricIdentifier should be just one per query. visQuery.labels.push(getLabel(expr, node)); const err = node.getChild(ErrorId); diff --git a/yarn.lock b/yarn.lock index 3e7091f9eb7..1761c4ae9bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3563,8 +3563,8 @@ __metadata: "@leeoniya/ufuzzy": "npm:1.0.14" "@lezer/common": "npm:1.2.1" "@lezer/highlight": "npm:1.2.0" - "@lezer/lr": "npm:1.4.0" - "@prometheus-io/lezer-promql": "npm:0.52.1" + "@lezer/lr": "npm:1.4.2" + "@prometheus-io/lezer-promql": "npm:0.53.1" "@reduxjs/toolkit": "npm:2.2.7" "@rollup/plugin-image": "npm:3.0.3" "@rollup/plugin-node-resolve": "npm:15.2.3" @@ -4622,12 +4622,12 @@ __metadata: languageName: node linkType: hard -"@lezer/lr@npm:1.4.0": - version: 1.4.0 - resolution: "@lezer/lr@npm:1.4.0" +"@lezer/lr@npm:1.4.2": + version: 1.4.2 + resolution: "@lezer/lr@npm:1.4.2" dependencies: "@lezer/common": "npm:^1.0.0" - checksum: 10/7391d0d08e54cd9e4f4d46e6ee6aa81fbaf079b22ed9c13d01fc9928e0ffd16d0c2d21b2cedd55675ad6c687277db28349ea8db81c9c69222cd7e7c40edd026e + checksum: 10/f7b505906c8d8df14c07866553cf3dae1e065b1da8b28fbb4193fd67ab8d187eb45f92759e29a2cfe4283296f0aa864b38a0a91708ecfc3e24b8f662d626e0c6 languageName: node linkType: hard @@ -5841,13 +5841,13 @@ __metadata: languageName: node linkType: hard -"@prometheus-io/lezer-promql@npm:0.52.1, @prometheus-io/lezer-promql@npm:^0.52.0": - version: 0.52.1 - resolution: "@prometheus-io/lezer-promql@npm:0.52.1" +"@prometheus-io/lezer-promql@npm:0.53.1": + version: 0.53.1 + resolution: "@prometheus-io/lezer-promql@npm:0.53.1" peerDependencies: "@lezer/highlight": ^1.1.2 "@lezer/lr": ^1.2.3 - checksum: 10/531164b8ddedbe0e0ba9304ea918f5cb0bb9c14f5e68c835fa02d3dbbe37c90daa8344a54666ba32aea6db8dfc0720cbc5b5ea50e1ed1126e987fcea7394b595 + checksum: 10/ebb506155f6343277e7bafc7342af3b8c0f162eda08c380f680f8c526878211a17be92e75b5d5de69cbf344f50a76221472b50533dde152ea396e148278c2d77 languageName: node linkType: hard @@ -17526,7 +17526,6 @@ __metadata: "@playwright/test": "npm:1.46.0" "@pmmmwh/react-refresh-webpack-plugin": "npm:0.5.15" "@popperjs/core": "npm:2.11.8" - "@prometheus-io/lezer-promql": "npm:^0.52.0" "@react-aria/dialog": "npm:3.5.16" "@react-aria/focus": "npm:3.18.1" "@react-aria/overlays": "npm:3.23.1" From faf7cb93127b9b39a87db84d094cc76263020e98 Mon Sep 17 00:00:00 2001 From: ismail simsek Date: Sun, 11 Aug 2024 23:48:36 +0200 Subject: [PATCH 005/229] Chore: Refactor explore metrics layout switcher and breakdown scene (#90944) * refactor breakdown scene * refactor BreakdownScene along with LayoutSwitcher * rename * don't pass default layout * better type handling * betterer --- .betterer.results | 5 +- .../trails/ActionTabs/BreakdownScene.tsx | 34 ++++---- .../trails/ActionTabs/LayoutSwitcher.tsx | 81 ++++++++++++------- .../app/features/trails/ActionTabs/types.ts | 12 ++- public/app/features/trails/MetricScene.tsx | 28 ++----- public/app/features/trails/interactions.ts | 4 +- 6 files changed, 93 insertions(+), 71 deletions(-) diff --git a/.betterer.results b/.betterer.results index 68d2a6b0b03..73851418191 100644 --- a/.betterer.results +++ b/.betterer.results @@ -5441,9 +5441,8 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], "public/app/features/trails/MetricScene.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], "public/app/features/trails/MetricSelect/MetricSelectScene.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], diff --git a/public/app/features/trails/ActionTabs/BreakdownScene.tsx b/public/app/features/trails/ActionTabs/BreakdownScene.tsx index 564506f066f..d5b742308d0 100644 --- a/public/app/features/trails/ActionTabs/BreakdownScene.tsx +++ b/public/app/features/trails/ActionTabs/BreakdownScene.tsx @@ -37,6 +37,7 @@ import { AddToFiltersGraphAction } from './AddToFiltersGraphAction'; import { ByFrameRepeater } from './ByFrameRepeater'; import { LayoutSwitcher } from './LayoutSwitcher'; import { breakdownPanelOptions } from './panelConfigs'; +import { BreakdownLayoutChangeCallback, BreakdownLayoutType } from './types'; import { getLabelOptions } from './utils'; import { BreakdownAxisChangeEvent, yAxisSyncBehavior } from './yAxisSyncBehavior'; @@ -97,13 +98,6 @@ export class BreakdownScene extends SceneObjectBase { this.clearBreakdownPanelAxisValues(); }); - metricScene.subscribeToState(({ layout }, old) => { - if (layout !== old.layout) { - // Change in layout will set up a different set of panel objects that haven't received the current yaxis range - this.clearBreakdownPanelAxisValues(); - } - }); - this.updateBody(variable); } @@ -186,8 +180,8 @@ export class BreakdownScene extends SceneObjectBase { if (!variable.state.loading && variable.state.options.length) { stateUpdate.body = variable.hasAllValue() - ? buildAllLayout(options, this._query!) - : buildNormalLayout(this._query!); + ? buildAllLayout(options, this._query!, this.onBreakdownLayoutChange) + : buildNormalLayout(this._query!, this.onBreakdownLayoutChange); } else if (!variable.state.loading) { stateUpdate.body = undefined; stateUpdate.blockingMessage = 'Unable to retrieve label options for currently selected metric.'; @@ -198,6 +192,10 @@ export class BreakdownScene extends SceneObjectBase { this.setState(stateUpdate); } + public onBreakdownLayoutChange = (_: BreakdownLayoutType) => { + this.clearBreakdownPanelAxisValues(); + }; + public onChange = (value?: string) => { if (!value) { return; @@ -271,7 +269,11 @@ function getStyles(theme: GrafanaTheme2) { }; } -export function buildAllLayout(options: Array>, queryDef: AutoQueryDef) { +export function buildAllLayout( + options: Array>, + queryDef: AutoQueryDef, + onBreakdownLayoutChange: BreakdownLayoutChangeCallback +) { const children: SceneFlexItemLike[] = []; for (const option of options) { @@ -318,11 +320,12 @@ export function buildAllLayout(options: Array>, queryDef ); } return new LayoutSwitcher({ - options: [ + breakdownLayoutOptions: [ { value: 'grid', label: 'Grid' }, { value: 'rows', label: 'Rows' }, ], - layouts: [ + onBreakdownLayoutChange, + breakdownLayouts: [ new SceneCSSGridLayout({ templateColumns: GRID_TEMPLATE_COLUMNS, autoRows: '200px', @@ -342,7 +345,7 @@ export function buildAllLayout(options: Array>, queryDef const GRID_TEMPLATE_COLUMNS = 'repeat(auto-fit, minmax(400px, 1fr))'; -function buildNormalLayout(queryDef: AutoQueryDef) { +function buildNormalLayout(queryDef: AutoQueryDef, onBreakdownLayoutChange: BreakdownLayoutChangeCallback) { const unit = queryDef.unit; function getLayoutChild(data: PanelData, frame: DataFrame, frameIndex: number): SceneFlexItem { @@ -377,12 +380,13 @@ function buildNormalLayout(queryDef: AutoQueryDef) { maxDataPoints: 300, queries: queryDef.queries, }), - options: [ + breakdownLayoutOptions: [ { value: 'single', label: 'Single' }, { value: 'grid', label: 'Grid' }, { value: 'rows', label: 'Rows' }, ], - layouts: [ + onBreakdownLayoutChange, + breakdownLayouts: [ new SceneFlexLayout({ direction: 'column', children: [ diff --git a/public/app/features/trails/ActionTabs/LayoutSwitcher.tsx b/public/app/features/trails/ActionTabs/LayoutSwitcher.tsx index b1f621b03c7..6c2bd614643 100644 --- a/public/app/features/trails/ActionTabs/LayoutSwitcher.tsx +++ b/public/app/features/trails/ActionTabs/LayoutSwitcher.tsx @@ -1,58 +1,85 @@ import { SelectableValue } from '@grafana/data'; -import { SceneComponentProps, sceneGraph, SceneObject, SceneObjectBase, SceneObjectState } from '@grafana/scenes'; +import { + SceneComponentProps, + SceneObject, + SceneObjectBase, + SceneObjectState, + SceneObjectUrlSyncConfig, + SceneObjectUrlValues, + SceneObjectWithUrlSync, +} from '@grafana/scenes'; import { Field, RadioButtonGroup } from '@grafana/ui'; -import { MetricScene } from '../MetricScene'; import { reportExploreMetrics } from '../interactions'; -import { TRAIL_BREAKDOWN_VIEW_KEY } from '../shared'; +import { MakeOptional, TRAIL_BREAKDOWN_VIEW_KEY } from '../shared'; -import { LayoutType } from './types'; +import { isBreakdownLayoutType, BreakdownLayoutChangeCallback, BreakdownLayoutType } from './types'; export interface LayoutSwitcherState extends SceneObjectState { - layouts: SceneObject[]; - options: Array>; + activeBreakdownLayout: BreakdownLayoutType; + breakdownLayouts: SceneObject[]; + breakdownLayoutOptions: Array>; + onBreakdownLayoutChange: BreakdownLayoutChangeCallback; } -export class LayoutSwitcher extends SceneObjectBase { - private getMetricScene() { - return sceneGraph.getAncestor(this, MetricScene); +export class LayoutSwitcher extends SceneObjectBase implements SceneObjectWithUrlSync { + protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['breakdownLayout'] }); + + public constructor(state: MakeOptional) { + const storedBreakdownLayout = localStorage.getItem(TRAIL_BREAKDOWN_VIEW_KEY); + super({ + activeBreakdownLayout: isBreakdownLayoutType(storedBreakdownLayout) ? storedBreakdownLayout : 'grid', + ...state, + }); + } + + getUrlState() { + return { breakdownLayout: this.state.activeBreakdownLayout }; + } + + updateFromUrl(values: SceneObjectUrlValues) { + const newBreakdownLayout = values.breakdownLayout; + if (newBreakdownLayout === 'string' && isBreakdownLayoutType(newBreakdownLayout)) { + if (this.state.activeBreakdownLayout !== newBreakdownLayout) { + this.setState({ activeBreakdownLayout: newBreakdownLayout }); + } + } } public Selector({ model }: { model: LayoutSwitcher }) { - const { options } = model.useState(); - const activeLayout = model.useActiveLayout(); + const { activeBreakdownLayout, breakdownLayoutOptions } = model.useState(); return ( - + ); } - private useActiveLayout() { - const { options } = this.useState(); - const { layout } = this.getMetricScene().useState(); - - const activeLayout = options.map((option) => option.value).includes(layout) ? layout : options[0].value; - return activeLayout; - } + public onLayoutChange = (active: BreakdownLayoutType) => { + if (this.state.activeBreakdownLayout === active) { + return; + } - public onLayoutChange = (layout: LayoutType) => { - reportExploreMetrics('breakdown_layout_changed', { layout }); - localStorage.setItem(TRAIL_BREAKDOWN_VIEW_KEY, layout); - this.getMetricScene().setState({ layout }); + reportExploreMetrics('breakdown_layout_changed', { layout: active }); + localStorage.setItem(TRAIL_BREAKDOWN_VIEW_KEY, active); + this.setState({ activeBreakdownLayout: active }); + this.state.onBreakdownLayoutChange(active); }; public static Component = ({ model }: SceneComponentProps) => { - const { layouts, options } = model.useState(); - const activeLayout = model.useActiveLayout(); + const { breakdownLayouts, breakdownLayoutOptions, activeBreakdownLayout } = model.useState(); - const index = options.findIndex((o) => o.value === activeLayout); + const index = breakdownLayoutOptions.findIndex((o) => o.value === activeBreakdownLayout); if (index === -1) { return null; } - const layout = layouts[index]; + const layout = breakdownLayouts[index]; return ; }; diff --git a/public/app/features/trails/ActionTabs/types.ts b/public/app/features/trails/ActionTabs/types.ts index d65482c4a29..4ee5fa1c5ee 100644 --- a/public/app/features/trails/ActionTabs/types.ts +++ b/public/app/features/trails/ActionTabs/types.ts @@ -1,7 +1,11 @@ -const LAYOUT_TYPES = ['single', 'grid', 'rows'] as const; +const BREAKDOWN_LAYOUT_TYPES = ['single', 'grid', 'rows'] as const; -export type LayoutType = (typeof LAYOUT_TYPES)[number]; +export type BreakdownLayoutType = (typeof BREAKDOWN_LAYOUT_TYPES)[number]; -export function isLayoutType(layoutType: string | null | undefined): layoutType is LayoutType { - return !!layoutType && layoutType in LAYOUT_TYPES; +export function isBreakdownLayoutType( + breakdownLayoutType: string | null | undefined +): breakdownLayoutType is BreakdownLayoutType { + return !!breakdownLayoutType && breakdownLayoutType in BREAKDOWN_LAYOUT_TYPES; } + +export type BreakdownLayoutChangeCallback = (newBreakdownLayout: BreakdownLayoutType) => void; diff --git a/public/app/features/trails/MetricScene.tsx b/public/app/features/trails/MetricScene.tsx index 65c43546b20..698207e53e5 100644 --- a/public/app/features/trails/MetricScene.tsx +++ b/public/app/features/trails/MetricScene.tsx @@ -3,23 +3,22 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; import { - SceneObjectState, - SceneObjectBase, + QueryVariable, SceneComponentProps, + sceneGraph, + SceneObjectBase, + SceneObjectState, SceneObjectUrlSyncConfig, SceneObjectUrlValues, - sceneGraph, SceneVariableSet, - QueryVariable, } from '@grafana/scenes'; -import { ToolbarButton, Box, Stack, Icon, TabsBar, Tab, useStyles2, LinkButton, Tooltip } from '@grafana/ui'; +import { Box, Icon, LinkButton, Stack, Tab, TabsBar, ToolbarButton, Tooltip, useStyles2 } from '@grafana/ui'; import { getExploreUrl } from '../../core/utils/explore'; import { buildBreakdownActionScene } from './ActionTabs/BreakdownScene'; import { buildMetricOverviewScene } from './ActionTabs/MetricOverviewScene'; import { buildRelatedMetricsScene } from './ActionTabs/RelatedMetricsScene'; -import { isLayoutType, LayoutType } from './ActionTabs/types'; import { getAutoQueriesForMetric } from './AutomaticMetricQueries/AutoQueryEngine'; import { AutoQueryDef, AutoQueryInfo } from './AutomaticMetricQueries/types'; import { MAIN_PANEL_MAX_HEIGHT, MAIN_PANEL_MIN_HEIGHT, MetricGraphScene } from './MetricGraphScene'; @@ -32,7 +31,6 @@ import { getVariablesWithMetricConstant, MakeOptional, MetricSelectedEvent, - TRAIL_BREAKDOWN_VIEW_KEY, trailDS, VAR_GROUP_BY, VAR_METRIC_EXPR, @@ -43,24 +41,21 @@ export interface MetricSceneState extends SceneObjectState { body: MetricGraphScene; metric: string; actionView?: string; - layout: LayoutType; autoQuery: AutoQueryInfo; queryDef?: AutoQueryDef; } export class MetricScene extends SceneObjectBase { - protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['actionView', 'layout'] }); + protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['actionView'] }); - public constructor(state: MakeOptional) { + public constructor(state: MakeOptional) { const autoQuery = state.autoQuery ?? getAutoQueriesForMetric(state.metric); - const layout = localStorage.getItem(TRAIL_BREAKDOWN_VIEW_KEY); super({ $variables: state.$variables ?? getVariableSet(state.metric), body: state.body ?? new MetricGraphScene({}), autoQuery, queryDef: state.queryDef ?? autoQuery.main, - layout: isLayoutType(layout) ? layout : 'grid', ...state, }); @@ -74,7 +69,7 @@ export class MetricScene extends SceneObjectBase { } getUrlState() { - return { actionView: this.state.actionView, layout: this.state.layout }; + return { actionView: this.state.actionView }; } updateFromUrl(values: SceneObjectUrlValues) { @@ -88,13 +83,6 @@ export class MetricScene extends SceneObjectBase { } else if (values.actionView === null) { this.setActionView(undefined); } - - if (typeof values.layout === 'string') { - const newLayout = values.layout as LayoutType; - if (this.state.layout !== newLayout) { - this.setState({ layout: newLayout }); - } - } } public setActionView(actionView?: ActionViewType) { diff --git a/public/app/features/trails/interactions.ts b/public/app/features/trails/interactions.ts index 206b0394526..b1903721da8 100644 --- a/public/app/features/trails/interactions.ts +++ b/public/app/features/trails/interactions.ts @@ -1,7 +1,7 @@ import { AdHocVariableFilter } from '@grafana/data'; import { reportInteraction } from '@grafana/runtime'; -import { LayoutType } from './ActionTabs/types'; +import { BreakdownLayoutType } from './ActionTabs/types'; import { TrailStepType } from './DataTrailsHistory'; import { ActionViewType } from './shared'; @@ -26,7 +26,7 @@ type Interactions = { cause: 'breakdown' | 'adhoc_filter'; }; // User changed the breakdown layout - breakdown_layout_changed: { layout: LayoutType }; + breakdown_layout_changed: { layout: BreakdownLayoutType }; // A metric exploration has started due to one of the following causes exploration_started: { cause: ( From 21d4a4f49ee4d74119089c5c2e2d2d6878f4faa0 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Mon, 12 Aug 2024 09:26:53 +0300 Subject: [PATCH 006/229] Auth: use IdentityType from authlib (#91763) --- pkg/api/admin_users.go | 3 +- pkg/api/dashboard.go | 3 +- pkg/api/folder.go | 3 +- pkg/api/index.go | 3 +- pkg/api/org.go | 3 +- pkg/api/pluginproxy/ds_proxy_test.go | 3 +- pkg/api/pluginproxy/pluginproxy_test.go | 4 +- pkg/api/user.go | 9 ++-- pkg/api/user_token.go | 5 +- pkg/apimachinery/identity/requester.go | 6 +-- pkg/apimachinery/identity/static.go | 4 +- pkg/apimachinery/identity/typed_id.go | 54 ++++--------------- pkg/apimachinery/identity/wrapper.go | 2 +- pkg/apiserver/endpoints/filters/requester.go | 5 +- pkg/apiserver/go.mod | 1 + pkg/apiserver/go.sum | 2 + .../apis/dashboard/legacy/sql_dashboards.go | 5 +- pkg/registry/apis/dashboard/sub_dto.go | 3 +- pkg/services/accesscontrol/accesscontrol.go | 3 +- pkg/services/accesscontrol/acimpl/service.go | 3 +- .../acimpl/service_bench_test.go | 3 +- .../accesscontrol/acimpl/service_test.go | 17 +++--- .../accesscontrol/authorize_in_org_test.go | 3 +- pkg/services/accesscontrol/cacheutils_test.go | 12 ++--- .../accesscontrol/database/database_test.go | 3 +- pkg/services/anonymous/anonimpl/client.go | 5 +- pkg/services/auth/idimpl/service.go | 3 +- pkg/services/auth/idimpl/service_test.go | 5 +- pkg/services/authn/authn.go | 3 +- pkg/services/authn/authnimpl/service.go | 9 ++-- pkg/services/authn/authnimpl/service_test.go | 15 +++--- .../authn/authnimpl/sync/oauth_token_sync.go | 4 +- pkg/services/authn/authnimpl/sync/org_sync.go | 6 +-- .../authn/authnimpl/sync/rbac_sync.go | 4 +- .../authn/authnimpl/sync/rbac_sync_test.go | 19 +++---- .../authn/authnimpl/sync/user_sync.go | 19 +++---- .../authn/authnimpl/sync/user_sync_test.go | 7 +-- pkg/services/authn/authntest/mock.go | 7 +-- pkg/services/authn/clients/api_key.go | 15 +++--- pkg/services/authn/clients/ext_jwt.go | 8 +-- pkg/services/authn/clients/ext_jwt_test.go | 7 +-- pkg/services/authn/clients/grafana.go | 3 +- pkg/services/authn/clients/oauth_test.go | 3 +- pkg/services/authn/clients/proxy.go | 5 +- pkg/services/authn/clients/proxy_test.go | 3 +- pkg/services/authn/clients/render.go | 5 +- pkg/services/authn/clients/session.go | 3 +- pkg/services/authn/identity.go | 14 ++--- pkg/services/contexthandler/contexthandler.go | 7 +-- .../contexthandler/contexthandler_test.go | 3 +- pkg/services/dashboards/database/database.go | 4 +- .../dashboards/service/dashboard_service.go | 5 +- .../dashboardsnapshots/database/database.go | 3 +- pkg/services/folder/folderimpl/sqlstore.go | 6 +-- pkg/services/oauthtoken/oauth_token.go | 5 +- .../user_header_middleware.go | 3 +- pkg/services/serviceaccounts/api/api.go | 3 +- pkg/services/team/teamapi/team.go | 4 +- pkg/services/user/identity.go | 26 ++++----- pkg/services/user/userimpl/verifier.go | 3 +- pkg/storage/unified/apistore/store_test.go | 3 +- pkg/storage/unified/resource/go.mod | 1 + pkg/storage/unified/resource/go.sum | 2 + .../unified/resource/grpc/authenticator.go | 3 +- .../resource/grpc/authenticator_test.go | 3 +- pkg/storage/unified/resource/server.go | 3 +- pkg/storage/unified/resource/server_test.go | 3 +- .../unified/sql/test/integration_test.go | 5 +- pkg/util/proxyutil/proxyutil.go | 3 +- pkg/util/proxyutil/proxyutil_test.go | 8 +-- 70 files changed, 229 insertions(+), 211 deletions(-) diff --git a/pkg/api/admin_users.go b/pkg/api/admin_users.go index 5efd9a9446d..83b1ae516af 100644 --- a/pkg/api/admin_users.go +++ b/pkg/api/admin_users.go @@ -8,6 +8,7 @@ import ( "golang.org/x/sync/errgroup" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -366,7 +367,7 @@ func (hs *HTTPServer) AdminLogoutUser(c *contextmodel.ReqContext) response.Respo return response.Error(http.StatusBadRequest, "id is invalid", err) } - if c.SignedInUser.GetID() == identity.NewTypedID(identity.TypeUser, userID) { + if c.SignedInUser.GetID() == identity.NewTypedID(claims.TypeUser, userID) { return response.Error(http.StatusBadRequest, "You cannot logout yourself", nil) } diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index c383d5c0e0a..97a8693fb22 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -11,6 +11,7 @@ import ( "strconv" "strings" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/apierrors" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -43,7 +44,7 @@ func (hs *HTTPServer) isDashboardStarredByUser(c *contextmodel.ReqContext, dashI return false, nil } - if !identity.IsIdentityType(c.SignedInUser.GetID(), identity.TypeUser) { + if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { return false, nil } diff --git a/pkg/api/folder.go b/pkg/api/folder.go index 1046ad7ed85..3b379cd5291 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/apierrors" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" @@ -194,7 +195,7 @@ func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int var permissions []accesscontrol.SetResourcePermissionCommand - if identity.IsIdentityType(user.GetID(), identity.TypeUser) { + if identity.IsIdentityType(user.GetID(), claims.TypeUser) { userID, err := identity.UserIdentifier(user.GetID()) if err != nil { return err diff --git a/pkg/api/index.go b/pkg/api/index.go index 14a64f76a09..8f1eebad990 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -8,6 +8,7 @@ import ( "net/http" "strings" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/webassets" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -170,7 +171,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV func (hs *HTTPServer) buildUserAnalyticsSettings(c *contextmodel.ReqContext) dtos.AnalyticsSettings { // Anonymous users do not have an email or auth info - if !identity.IsIdentityType(c.SignedInUser.GetID(), identity.TypeUser) { + if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { return dtos.AnalyticsSettings{Identifier: "@" + hs.Cfg.AppURL} } diff --git a/pkg/api/org.go b/pkg/api/org.go index 34b08544b11..88181f7e48a 100644 --- a/pkg/api/org.go +++ b/pkg/api/org.go @@ -6,6 +6,7 @@ import ( "net/http" "strconv" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -132,7 +133,7 @@ func (hs *HTTPServer) CreateOrg(c *contextmodel.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "bad request data", err) } - if !identity.IsIdentityType(c.SignedInUser.GetID(), identity.TypeUser) { + if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { return response.Error(http.StatusForbidden, "Only users can create organizations", nil) } diff --git a/pkg/api/pluginproxy/ds_proxy_test.go b/pkg/api/pluginproxy/ds_proxy_test.go index 16203412fce..7ba804a41be 100644 --- a/pkg/api/pluginproxy/ds_proxy_test.go +++ b/pkg/api/pluginproxy/ds_proxy_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/oauth2" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/datasource" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/components/simplejson" @@ -586,7 +587,7 @@ func TestDataSourceProxy_routeRule(t *testing.T) { &contextmodel.ReqContext{ SignedInUser: &user.SignedInUser{ Login: "test_user", - FallbackType: identity.TypeUser, + FallbackType: claims.TypeUser, UserID: 1, }, }, diff --git a/pkg/api/pluginproxy/pluginproxy_test.go b/pkg/api/pluginproxy/pluginproxy_test.go index 2a89f18a27d..c1a8523f942 100644 --- a/pkg/api/pluginproxy/pluginproxy_test.go +++ b/pkg/api/pluginproxy/pluginproxy_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" @@ -79,7 +79,7 @@ func TestPluginProxy(t *testing.T) { &contextmodel.ReqContext{ SignedInUser: &user.SignedInUser{ Login: "test_user", - FallbackType: identity.TypeUser, + FallbackType: claims.TypeUser, UserID: 1, }, Context: &web.Context{ diff --git a/pkg/api/user.go b/pkg/api/user.go index 42baf886680..6179f71f0b2 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -31,7 +32,7 @@ import ( // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) GetSignedInUser(c *contextmodel.ReqContext) response.Response { - if !identity.IsIdentityType(c.GetID(), identity.TypeUser) { + if !identity.IsIdentityType(c.GetID(), claims.TypeUser) { return response.JSON(http.StatusOK, user.UserProfileDTO{ IsGrafanaAdmin: c.SignedInUser.GetIsGrafanaAdmin(), OrgID: c.SignedInUser.GetOrgID(), @@ -277,7 +278,7 @@ func (hs *HTTPServer) handleUpdateUser(ctx context.Context, cmd user.UpdateUserC } func (hs *HTTPServer) StartEmailVerificaton(c *contextmodel.ReqContext) response.Response { - if !identity.IsIdentityType(c.SignedInUser.GetID(), identity.TypeUser) { + if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { return response.Error(http.StatusBadRequest, "Only users can verify their email", nil) } @@ -504,7 +505,7 @@ func (hs *HTTPServer) ChangeActiveOrgAndRedirectToHome(c *contextmodel.ReqContex return } - if !identity.IsIdentityType(c.SignedInUser.GetID(), identity.TypeUser) { + if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { c.JsonApiErr(http.StatusForbidden, "Endpoint only available for users", nil) return } @@ -629,7 +630,7 @@ func (hs *HTTPServer) ClearHelpFlags(c *contextmodel.ReqContext) response.Respon } func getUserID(c *contextmodel.ReqContext) (int64, *response.NormalResponse) { - if !identity.IsIdentityType(c.SignedInUser.GetID(), identity.TypeUser) { + if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { return 0, response.Error(http.StatusForbidden, "Endpoint only available for users", nil) } diff --git a/pkg/api/user_token.go b/pkg/api/user_token.go index c168c590784..41ba01e7a11 100644 --- a/pkg/api/user_token.go +++ b/pkg/api/user_token.go @@ -8,6 +8,7 @@ import ( "github.com/ua-parser/uap-go/uaparser" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -32,7 +33,7 @@ import ( // 403: forbiddenError // 500: internalServerError func (hs *HTTPServer) GetUserAuthTokens(c *contextmodel.ReqContext) response.Response { - if !identity.IsIdentityType(c.SignedInUser.GetID(), identity.TypeUser) { + if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { return response.Error(http.StatusForbidden, "entity not allowed to get tokens", nil) } @@ -62,7 +63,7 @@ func (hs *HTTPServer) RevokeUserAuthToken(c *contextmodel.ReqContext) response.R return response.Error(http.StatusBadRequest, "bad request data", err) } - if !identity.IsIdentityType(c.SignedInUser.GetID(), identity.TypeUser) { + if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { return response.Error(http.StatusForbidden, "entity not allowed to revoke tokens", nil) } diff --git a/pkg/apimachinery/identity/requester.go b/pkg/apimachinery/identity/requester.go index c8f110a5a2e..ff1e248368d 100644 --- a/pkg/apimachinery/identity/requester.go +++ b/pkg/apimachinery/identity/requester.go @@ -14,7 +14,7 @@ type Requester interface { claims.AuthInfo // GetIdentityType returns the type for the requester - GetIdentityType() IdentityType + GetIdentityType() claims.IdentityType // GetRawIdentifier returns only the identifier part of the UID, excluding the type GetRawIdentifier() string // Deprecated: use GetUID instead @@ -82,7 +82,7 @@ type Requester interface { // Applicable for users, service accounts, api keys and renderer service. // Errors if the identifier is not initialized or if type is not recognized. func IntIdentifier(typedID TypedID) (int64, error) { - if IsIdentityType(typedID, TypeUser, TypeAPIKey, TypeServiceAccount, TypeRenderService) { + if claims.IsIdentityType(typedID.t, claims.TypeUser, claims.TypeAPIKey, claims.TypeServiceAccount, claims.TypeRenderService) { id, err := strconv.ParseInt(typedID.ID(), 10, 64) if err != nil { return 0, fmt.Errorf("unrecognized format for valid type %s: %w", typedID.Type(), err) @@ -107,7 +107,7 @@ func UserIdentifier(typedID TypedID) (int64, error) { return 0, err } - if IsIdentityType(typedID, TypeUser, TypeServiceAccount) { + if claims.IsIdentityType(typedID.t, claims.TypeUser, claims.TypeServiceAccount) { return userID, nil } diff --git a/pkg/apimachinery/identity/static.go b/pkg/apimachinery/identity/static.go index 3fc5aec3430..5d4f25e9a8a 100644 --- a/pkg/apimachinery/identity/static.go +++ b/pkg/apimachinery/identity/static.go @@ -14,7 +14,7 @@ var _ Requester = &StaticRequester{} // This is mostly copied from: // https://github.com/grafana/grafana/blob/v11.0.0/pkg/services/user/identity.go#L16 type StaticRequester struct { - Type IdentityType + Type claims.IdentityType UserID int64 UserUID string OrgID int64 @@ -65,7 +65,7 @@ func (u *StaticRequester) GetInternalID() (int64, error) { } // GetIdentityType implements Requester. -func (u *StaticRequester) GetIdentityType() IdentityType { +func (u *StaticRequester) GetIdentityType() claims.IdentityType { return u.Type } diff --git a/pkg/apimachinery/identity/typed_id.go b/pkg/apimachinery/identity/typed_id.go index 4cecbd2cc93..a2e81179396 100644 --- a/pkg/apimachinery/identity/typed_id.go +++ b/pkg/apimachinery/identity/typed_id.go @@ -4,46 +4,12 @@ import ( "fmt" "strconv" "strings" -) -type IdentityType string - -const ( - TypeUser IdentityType = "user" - TypeAPIKey IdentityType = "api-key" - TypeServiceAccount IdentityType = "service-account" - TypeAnonymous IdentityType = "anonymous" - TypeRenderService IdentityType = "render" - TypeAccessPolicy IdentityType = "access-policy" - TypeProvisioning IdentityType = "provisioning" - TypeEmpty IdentityType = "" + "github.com/grafana/authlib/claims" ) -func (n IdentityType) String() string { - return string(n) -} - -func ParseType(str string) (IdentityType, error) { - switch str { - case string(TypeUser): - return TypeUser, nil - case string(TypeAPIKey): - return TypeAPIKey, nil - case string(TypeServiceAccount): - return TypeServiceAccount, nil - case string(TypeAnonymous): - return TypeAnonymous, nil - case string(TypeRenderService): - return TypeRenderService, nil - case string(TypeAccessPolicy): - return TypeAccessPolicy, nil - default: - return "", ErrInvalidTypedID.Errorf("got invalid identity type %s", str) - } -} - // IsIdentityType returns true if typedID matches any expected identity type -func IsIdentityType(typedID TypedID, expected ...IdentityType) bool { +func IsIdentityType(typedID TypedID, expected ...claims.IdentityType) bool { for _, e := range expected { if typedID.Type() == e { return true @@ -53,7 +19,7 @@ func IsIdentityType(typedID TypedID, expected ...IdentityType) bool { return false } -var AnonymousTypedID = NewTypedID(TypeAnonymous, 0) +var AnonymousTypedID = NewTypedID(claims.TypeAnonymous, 0) func ParseTypedID(str string) (TypedID, error) { var typeID TypedID @@ -63,7 +29,7 @@ func ParseTypedID(str string) (TypedID, error) { return typeID, ErrInvalidTypedID.Errorf("expected typed id to have 2 parts") } - t, err := ParseType(parts[0]) + t, err := claims.ParseType(parts[0]) if err != nil { return typeID, err } @@ -84,7 +50,7 @@ func MustParseTypedID(str string) TypedID { return typeID } -func NewTypedID(t IdentityType, id int64) TypedID { +func NewTypedID(t claims.IdentityType, id int64) TypedID { return TypedID{ id: strconv.FormatInt(id, 10), t: t, @@ -92,7 +58,7 @@ func NewTypedID(t IdentityType, id int64) TypedID { } // NewTypedIDString creates a new TypedID with a string id -func NewTypedIDString(t IdentityType, id string) TypedID { +func NewTypedIDString(t claims.IdentityType, id string) TypedID { return TypedID{ id: id, t: t, @@ -102,7 +68,7 @@ func NewTypedIDString(t IdentityType, id string) TypedID { // FIXME: use this instead of encoded string through the codebase type TypedID struct { id string - t IdentityType + t claims.IdentityType } func (ni TypedID) ID() string { @@ -112,7 +78,7 @@ func (ni TypedID) ID() string { // UserID will try to parse and int64 identifier if namespace is either user or service-account. // For all other namespaces '0' will be returned. func (ni TypedID) UserID() (int64, error) { - if ni.IsType(TypeUser, TypeServiceAccount) { + if ni.IsType(claims.TypeUser, claims.TypeServiceAccount) { return ni.ParseInt() } return 0, nil @@ -123,11 +89,11 @@ func (ni TypedID) ParseInt() (int64, error) { return strconv.ParseInt(ni.id, 10, 64) } -func (ni TypedID) Type() IdentityType { +func (ni TypedID) Type() claims.IdentityType { return ni.t } -func (ni TypedID) IsType(expected ...IdentityType) bool { +func (ni TypedID) IsType(expected ...claims.IdentityType) bool { return IsIdentityType(ni, expected...) } diff --git a/pkg/apimachinery/identity/wrapper.go b/pkg/apimachinery/identity/wrapper.go index 07e2b3b95e1..03da34e7aaa 100644 --- a/pkg/apimachinery/identity/wrapper.go +++ b/pkg/apimachinery/identity/wrapper.go @@ -35,7 +35,7 @@ func (i *IDClaimsWrapper) EmailVerified() bool { // GetIdentityType implements claims.IdentityClaims. func (i *IDClaimsWrapper) IdentityType() claims.IdentityType { - return claims.IdentityType(i.Source.GetIdentityType()) + return i.Source.GetIdentityType() } // GetInternalID implements claims.IdentityClaims. diff --git a/pkg/apiserver/endpoints/filters/requester.go b/pkg/apiserver/endpoints/filters/requester.go index 71bb45e19c0..63baf363619 100644 --- a/pkg/apiserver/endpoints/filters/requester.go +++ b/pkg/apiserver/endpoints/filters/requester.go @@ -8,6 +8,7 @@ import ( "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/klog/v2" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" ) @@ -26,7 +27,7 @@ func WithRequester(handler http.Handler) http.Handler { if ok { if info.GetName() == user.Anonymous { requester = &identity.StaticRequester{ - Type: identity.TypeAnonymous, + Type: claims.TypeAnonymous, Name: info.GetName(), Login: info.GetName(), Permissions: map[int64]map[string][]string{}, @@ -37,7 +38,7 @@ func WithRequester(handler http.Handler) http.Handler { slices.Contains(info.GetGroups(), user.SystemPrivilegedGroup) { orgId := int64(1) requester = &identity.StaticRequester{ - Type: identity.TypeServiceAccount, // system:apiserver + Type: claims.TypeServiceAccount, // system:apiserver UserID: 1, OrgID: orgId, Name: info.GetName(), diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 29939ea3ce1..46d483c36a0 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -4,6 +4,7 @@ go 1.22.4 require ( github.com/google/go-cmp v0.6.0 + github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 github.com/prometheus/client_golang v1.19.1 github.com/stretchr/testify v1.9.0 diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index ce3066b8d01..892365f6bea 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -77,6 +77,8 @@ 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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 h1:uD1LcKwvEAqzDsgVChBudPqo5BhPxkj9AgylT5QCReo= +github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 h1:ItDcDxUjVLPKja+hogpqgW/kj8LxUL2qscelXIsN1Bs= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1/go.mod h1:DkxMin+qOh1Fgkxfbt+CUfBqqsCQJMG9op8Os/irBPA= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 23938a00b23..85426711c63 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" dashboardsV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" @@ -338,10 +339,10 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) { func getUserID(v sql.NullString, id sql.NullInt64) string { if v.Valid && v.String != "" { - return identity.NewTypedIDString(identity.TypeUser, v.String).String() + return identity.NewTypedIDString(claims.TypeUser, v.String).String() } if id.Valid && id.Int64 == -1 { - return identity.NewTypedIDString(identity.TypeProvisioning, "").String() + return identity.NewTypedIDString(claims.TypeProvisioning, "").String() } return "" } diff --git a/pkg/registry/apis/dashboard/sub_dto.go b/pkg/registry/apis/dashboard/sub_dto.go index cba01d2724b..7e0cd3a60a1 100644 --- a/pkg/registry/apis/dashboard/sub_dto.go +++ b/pkg/registry/apis/dashboard/sub_dto.go @@ -9,6 +9,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" dashboard "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" @@ -86,7 +87,7 @@ func (r *DTOConnector) Connect(ctx context.Context, name string, opts runtime.Ob access.CanSave, _ = guardian.CanSave() access.CanAdmin, _ = guardian.CanAdmin() access.CanDelete, _ = guardian.CanDelete() - access.CanStar = user.GetID().Type() == identity.TypeUser // not anon + access.CanStar = user.GetID().Type() == claims.TypeUser // not anon access.AnnotationsPermissions = &dashboard.AnnotationPermission{} r.getAnnotationPermissionsByScope(ctx, user, &access.AnnotationsPermissions.Dashboard, accesscontrol.ScopeAnnotationsTypeDashboard) diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index 2c89d4e3e0f..a4a48dbb71f 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -9,6 +9,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/authn" @@ -110,7 +111,7 @@ func (s *SearchOptions) ComputeUserID() (int64, error) { } // Validate namespace type is user or service account - if s.TypedID.Type() != identity.TypeUser && s.TypedID.Type() != identity.TypeServiceAccount { + if s.TypedID.Type() != claims.TypeUser && s.TypedID.Type() != claims.TypeServiceAccount { return 0, fmt.Errorf("invalid type: %s", s.TypedID.Type()) } diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index c0641bbb118..07e9168bf6e 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -11,6 +11,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/attribute" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" @@ -211,7 +212,7 @@ func (s *Service) getUserDirectPermissions(ctx context.Context, user identity.Re defer span.End() var userID int64 - if identity.IsIdentityType(user.GetID(), identity.TypeUser, identity.TypeServiceAccount) { + if identity.IsIdentityType(user.GetID(), claims.TypeUser, claims.TypeServiceAccount) { var err error userID, err = identity.UserIdentifier(user.GetID()) if err != nil { diff --git a/pkg/services/accesscontrol/acimpl/service_bench_test.go b/pkg/services/accesscontrol/acimpl/service_bench_test.go index 20772c5c8a9..864676ae54b 100644 --- a/pkg/services/accesscontrol/acimpl/service_bench_test.go +++ b/pkg/services/accesscontrol/acimpl/service_bench_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/localcache" @@ -262,7 +263,7 @@ func benchSearchUserWithAction(b *testing.B, usersCount, resourceCount int) { for n := 0; n < b.N; n++ { usersPermissions, err := acService.SearchUsersPermissions(context.Background(), siu, - accesscontrol.SearchOptions{Action: "resources:action2", TypedID: identity.NewTypedID(identity.TypeUser, 14)}) + accesscontrol.SearchOptions{Action: "resources:action2", TypedID: identity.NewTypedID(claims.TypeUser, 14)}) require.NoError(b, err) require.Len(b, usersPermissions, 1) for _, permissions := range usersPermissions { diff --git a/pkg/services/accesscontrol/acimpl/service_test.go b/pkg/services/accesscontrol/acimpl/service_test.go index e5df3ada30c..a9713529f6b 100644 --- a/pkg/services/accesscontrol/acimpl/service_test.go +++ b/pkg/services/accesscontrol/acimpl/service_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/localcache" @@ -546,7 +547,7 @@ func TestService_SearchUsersPermissions(t *testing.T) { // only the user's basic roles and the user's stored permissions name: "check namespacedId filter works correctly", siuPermissions: listAllPerms, - searchOption: accesscontrol.SearchOptions{TypedID: identity.NewTypedID(identity.TypeServiceAccount, 1)}, + searchOption: accesscontrol.SearchOptions{TypedID: identity.NewTypedID(claims.TypeServiceAccount, 1)}, ramRoles: map[string]*accesscontrol.RoleDTO{ string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{ {Action: accesscontrol.ActionTeamsRead, Scope: "teams:*"}, @@ -618,7 +619,7 @@ func TestService_SearchUserPermissions(t *testing.T) { name: "ram only", searchOption: accesscontrol.SearchOptions{ ActionPrefix: "teams", - TypedID: identity.NewTypedID(identity.TypeUser, 2), + TypedID: identity.NewTypedID(claims.TypeUser, 2), }, ramRoles: map[string]*accesscontrol.RoleDTO{ string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{ @@ -643,7 +644,7 @@ func TestService_SearchUserPermissions(t *testing.T) { name: "stored only", searchOption: accesscontrol.SearchOptions{ ActionPrefix: "teams", - TypedID: identity.NewTypedID(identity.TypeUser, 2), + TypedID: identity.NewTypedID(claims.TypeUser, 2), }, storedPerms: map[int64][]accesscontrol.Permission{ 1: {{Action: accesscontrol.ActionTeamsRead, Scope: "teams:id:1"}}, @@ -663,7 +664,7 @@ func TestService_SearchUserPermissions(t *testing.T) { name: "ram and stored", searchOption: accesscontrol.SearchOptions{ ActionPrefix: "teams", - TypedID: identity.NewTypedID(identity.TypeUser, 2), + TypedID: identity.NewTypedID(claims.TypeUser, 2), }, ramRoles: map[string]*accesscontrol.RoleDTO{ string(identity.RoleAdmin): {Permissions: []accesscontrol.Permission{ @@ -693,7 +694,7 @@ func TestService_SearchUserPermissions(t *testing.T) { name: "check action prefix filter works correctly", searchOption: accesscontrol.SearchOptions{ ActionPrefix: "teams", - TypedID: identity.NewTypedID(identity.TypeUser, 1), + TypedID: identity.NewTypedID(claims.TypeUser, 1), }, ramRoles: map[string]*accesscontrol.RoleDTO{ string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{ @@ -715,7 +716,7 @@ func TestService_SearchUserPermissions(t *testing.T) { name: "check action filter works correctly", searchOption: accesscontrol.SearchOptions{ Action: accesscontrol.ActionTeamsRead, - TypedID: identity.NewTypedID(identity.TypeUser, 1), + TypedID: identity.NewTypedID(claims.TypeUser, 1), }, ramRoles: map[string]*accesscontrol.RoleDTO{ string(identity.RoleEditor): {Permissions: []accesscontrol.Permission{ @@ -737,7 +738,7 @@ func TestService_SearchUserPermissions(t *testing.T) { name: "check action sets are correctly included if an action is specified", searchOption: accesscontrol.SearchOptions{ Action: "dashboards:read", - TypedID: identity.NewTypedID(identity.TypeUser, 1), + TypedID: identity.NewTypedID(claims.TypeUser, 1), }, withActionSets: true, actionSets: map[string][]string{ @@ -770,7 +771,7 @@ func TestService_SearchUserPermissions(t *testing.T) { name: "check action sets are correctly included if an action prefix is specified", searchOption: accesscontrol.SearchOptions{ ActionPrefix: "dashboards", - TypedID: identity.NewTypedID(identity.TypeUser, 1), + TypedID: identity.NewTypedID(claims.TypeUser, 1), }, withActionSets: true, actionSets: map[string][]string{ diff --git a/pkg/services/accesscontrol/authorize_in_org_test.go b/pkg/services/accesscontrol/authorize_in_org_test.go index 44f41d37bb0..e85ced6c2bd 100644 --- a/pkg/services/accesscontrol/authorize_in_org_test.go +++ b/pkg/services/accesscontrol/authorize_in_org_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" @@ -187,7 +188,7 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/endpoint", nil) expectedIdentity := &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, tc.ctxSignedInUser.UserID), + ID: identity.NewTypedID(claims.TypeUser, tc.ctxSignedInUser.UserID), OrgID: tc.targetOrgId, Permissions: map[int64]map[string][]string{}, } diff --git a/pkg/services/accesscontrol/cacheutils_test.go b/pkg/services/accesscontrol/cacheutils_test.go index b65ec9485d6..7aff7941586 100644 --- a/pkg/services/accesscontrol/cacheutils_test.go +++ b/pkg/services/accesscontrol/cacheutils_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/user" ) @@ -21,7 +21,7 @@ func TestPermissionCacheKey(t *testing.T) { signedInUser: &user.SignedInUser{ OrgID: 1, UserID: 1, - FallbackType: identity.TypeUser, + FallbackType: claims.TypeUser, }, expected: "rbac-permissions-1-user-1", }, @@ -31,7 +31,7 @@ func TestPermissionCacheKey(t *testing.T) { OrgID: 1, ApiKeyID: 1, IsServiceAccount: false, - FallbackType: identity.TypeUser, + FallbackType: claims.TypeUser, }, expected: "rbac-permissions-1-api-key-1", }, @@ -41,7 +41,7 @@ func TestPermissionCacheKey(t *testing.T) { OrgID: 1, UserID: 1, IsServiceAccount: true, - FallbackType: identity.TypeUser, + FallbackType: claims.TypeUser, }, expected: "rbac-permissions-1-service-account-1", }, @@ -51,7 +51,7 @@ func TestPermissionCacheKey(t *testing.T) { OrgID: 1, UserID: -1, IsServiceAccount: true, - FallbackType: identity.TypeUser, // NOTE, this is still a service account! + FallbackType: claims.TypeUser, // NOTE, this is still a service account! }, expected: "rbac-permissions-1-service-account--1", }, @@ -60,7 +60,7 @@ func TestPermissionCacheKey(t *testing.T) { signedInUser: &user.SignedInUser{ OrgID: 1, OrgRole: org.RoleNone, - FallbackType: identity.TypeUser, + FallbackType: claims.TypeUser, }, expected: "rbac-permissions-1-user-None", }, diff --git a/pkg/services/accesscontrol/database/database_test.go b/pkg/services/accesscontrol/database/database_test.go index 8099a22e910..3ec2950d088 100644 --- a/pkg/services/accesscontrol/database/database_test.go +++ b/pkg/services/accesscontrol/database/database_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/localcache" @@ -626,7 +627,7 @@ func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) { }, options: accesscontrol.SearchOptions{ ActionPrefix: "teams:", - TypedID: identity.NewTypedID(identity.TypeUser, 1), + TypedID: identity.NewTypedID(claims.TypeUser, 1), }, wantPerm: map[int64][]accesscontrol.Permission{ 1: {{Action: "teams:read", Scope: "teams:id:1"}, {Action: "teams:read", Scope: "teams:id:10"}, diff --git a/pkg/services/anonymous/anonimpl/client.go b/pkg/services/anonymous/anonimpl/client.go index 587f1c2fd78..9af940ead3d 100644 --- a/pkg/services/anonymous/anonimpl/client.go +++ b/pkg/services/anonymous/anonimpl/client.go @@ -6,6 +6,7 @@ import ( "net/http" "strings" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" @@ -69,8 +70,8 @@ func (a *Anonymous) Test(ctx context.Context, r *authn.Request) bool { return true } -func (a *Anonymous) IdentityType() identity.IdentityType { - return identity.TypeAnonymous +func (a *Anonymous) IdentityType() claims.IdentityType { + return claims.TypeAnonymous } func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) { diff --git a/pkg/services/auth/idimpl/service.go b/pkg/services/auth/idimpl/service.go index 4f9e37f2618..1c696255b34 100644 --- a/pkg/services/auth/idimpl/service.go +++ b/pkg/services/auth/idimpl/service.go @@ -8,6 +8,7 @@ import ( "github.com/go-jose/go-jose/v3/jwt" authnlib "github.com/grafana/authlib/authn" + authnlibclaims "github.com/grafana/authlib/claims" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sync/singleflight" @@ -99,7 +100,7 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri }, } - if identity.IsIdentityType(id.GetID(), identity.TypeUser) { + if identity.IsIdentityType(id.GetID(), authnlibclaims.TypeUser) { claims.Rest.Email = id.GetEmail() claims.Rest.EmailVerified = id.IsEmailVerified() claims.Rest.AuthenticatedBy = id.GetAuthenticatedBy() diff --git a/pkg/services/auth/idimpl/service_test.go b/pkg/services/auth/idimpl/service_test.go index 3cb8b36a7b9..b0407743679 100644 --- a/pkg/services/auth/idimpl/service_test.go +++ b/pkg/services/auth/idimpl/service_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/services/auth" @@ -85,7 +86,7 @@ func TestService_SignIdentity(t *testing.T) { ID: identity.MustParseTypedID("user:1"), AuthenticatedBy: login.AzureADAuthModule, Login: "U1", - UID: identity.NewTypedIDString(identity.TypeUser, "edpu3nnt61se8e")}) + UID: identity.NewTypedIDString(claims.TypeUser, "edpu3nnt61se8e")}) require.NoError(t, err) parsed, err := jwt.ParseSigned(token) @@ -108,7 +109,7 @@ func TestService_SignIdentity(t *testing.T) { ID: identity.MustParseTypedID("user:1"), AuthenticatedBy: login.AzureADAuthModule, Login: "U1", - UID: identity.NewTypedIDString(identity.TypeUser, "edpu3nnt61se8e")}) + UID: identity.NewTypedIDString(claims.TypeUser, "edpu3nnt61se8e")}) require.NoError(t, err) assert.Equal(t, login.AzureADAuthModule, gotClaims.Rest.AuthenticatedBy) diff --git a/pkg/services/authn/authn.go b/pkg/services/authn/authn.go index af7e20923db..f7f2686a134 100644 --- a/pkg/services/authn/authn.go +++ b/pkg/services/authn/authn.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/middleware/cookies" @@ -179,7 +180,7 @@ type UsageStatClient interface { // Clients that implements this interface can resolve an full identity from an orgID and namespaceID. type IdentityResolverClient interface { Client - IdentityType() identity.IdentityType + IdentityType() claims.IdentityType ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*Identity, error) } diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 865ad9dca19..63f587e5ca4 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -12,6 +12,7 @@ import ( "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" @@ -217,7 +218,7 @@ func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (i } // Login is only supported for users - if !id.ID.IsType(identity.TypeUser) { + if !id.ID.IsType(claims.TypeUser) { s.metrics.failedLogin.WithLabelValues(client).Inc() return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", id.ID.Type()) } @@ -281,7 +282,7 @@ func (s *Service) Logout(ctx context.Context, user identity.Requester, sessionTo redirect.URL = s.cfg.SignoutRedirectUrl } - if !user.GetID().IsType(identity.TypeUser) { + if !user.GetID().IsType(claims.TypeUser) { return redirect, nil } @@ -380,7 +381,7 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID i ctx, span := s.tracer.Start(ctx, "authn.resolveIdentity") defer span.End() - if namespaceID.IsType(identity.TypeUser) { + if namespaceID.IsType(claims.TypeUser) { return &authn.Identity{ OrgID: orgID, ID: namespaceID, @@ -391,7 +392,7 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID i }}, nil } - if namespaceID.IsType(identity.TypeServiceAccount) { + if namespaceID.IsType(claims.TypeServiceAccount) { return &authn.Identity{ ID: namespaceID, OrgID: orgID, diff --git a/pkg/services/authn/authnimpl/service_test.go b/pkg/services/authn/authnimpl/service_test.go index ca34c134b4d..8eb0f49846c 100644 --- a/pkg/services/authn/authnimpl/service_test.go +++ b/pkg/services/authn/authnimpl/service_test.go @@ -15,6 +15,7 @@ import ( sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -420,31 +421,31 @@ func TestService_Logout(t *testing.T) { tests := []TestCase{ { desc: "should redirect to default redirect url when identity is not a user", - identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeServiceAccount, 1)}, + identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeServiceAccount, 1)}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, }, { desc: "should redirect to default redirect url when no external provider was used to authenticate", - identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1)}, + identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1)}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, expectedTokenRevoked: true, }, { desc: "should redirect to default redirect url when client is not found", - identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "notfound"}, + identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1), AuthenticatedBy: "notfound"}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, expectedTokenRevoked: true, }, { desc: "should redirect to default redirect url when client do not implement logout extension", - identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"}, + identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1), AuthenticatedBy: "azuread"}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"}, expectedTokenRevoked: true, }, { desc: "should use signout redirect url if configured", - identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"}, + identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1), AuthenticatedBy: "azuread"}, expectedRedirect: &authn.Redirect{URL: "some-url"}, client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"}, signoutRedirectURL: "some-url", @@ -452,7 +453,7 @@ func TestService_Logout(t *testing.T) { }, { desc: "should redirect to client specific url", - identity: &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), AuthenticatedBy: "azuread"}, + identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1), AuthenticatedBy: "azuread"}, expectedRedirect: &authn.Redirect{URL: "http://idp.com/logout"}, client: &authntest.MockClient{ NameFunc: func() string { return "auth.client.azuread" }, @@ -527,7 +528,7 @@ func TestService_ResolveIdentity(t *testing.T) { t.Run("should resolve for valid namespace if client is registered", func(t *testing.T) { svc := setupTests(t, func(svc *Service) { svc.RegisterClient(&authntest.MockClient{ - IdentityTypeFunc: func() identity.IdentityType { return identity.TypeAPIKey }, + IdentityTypeFunc: func() claims.IdentityType { return claims.TypeAPIKey }, ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) { return &authn.Identity{}, nil }, diff --git a/pkg/services/authn/authnimpl/sync/oauth_token_sync.go b/pkg/services/authn/authnimpl/sync/oauth_token_sync.go index 8262d290d93..cecc3cc5218 100644 --- a/pkg/services/authn/authnimpl/sync/oauth_token_sync.go +++ b/pkg/services/authn/authnimpl/sync/oauth_token_sync.go @@ -8,7 +8,7 @@ import ( "golang.org/x/sync/singleflight" - "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login/social" @@ -42,7 +42,7 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, id *authn.Ident defer span.End() // only perform oauth token check if identity is a user - if !id.ID.IsType(identity.TypeUser) { + if !id.ID.IsType(claims.TypeUser) { return nil } diff --git a/pkg/services/authn/authnimpl/sync/org_sync.go b/pkg/services/authn/authnimpl/sync/org_sync.go index 9fc5b1d3e7c..0ef1d5150d2 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync.go +++ b/pkg/services/authn/authnimpl/sync/org_sync.go @@ -6,7 +6,7 @@ import ( "fmt" "sort" - "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -39,7 +39,7 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a ctxLogger := s.log.FromContext(ctx).New("id", id.ID, "login", id.Login) - if !id.ID.IsType(identity.TypeUser) { + if !id.ID.IsType(claims.TypeUser) { ctxLogger.Warn("Failed to sync org role, invalid namespace for identity", "type", id.ID.Type()) return nil } @@ -145,7 +145,7 @@ func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn. ctxLogger := s.log.FromContext(ctx) - if !currentIdentity.ID.IsType(identity.TypeUser) { + if !currentIdentity.ID.IsType(claims.TypeUser) { ctxLogger.Debug("Skipping default org sync, not a user", "type", currentIdentity.ID.Type()) return } diff --git a/pkg/services/authn/authnimpl/sync/rbac_sync.go b/pkg/services/authn/authnimpl/sync/rbac_sync.go index 95d7b0b44b5..e9ed94ff0d6 100644 --- a/pkg/services/authn/authnimpl/sync/rbac_sync.go +++ b/pkg/services/authn/authnimpl/sync/rbac_sync.go @@ -6,8 +6,8 @@ import ( "golang.org/x/exp/maps" + "github.com/grafana/authlib/claims" "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/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -148,7 +148,7 @@ func (s *RBACSync) SyncCloudRoles(ctx context.Context, ident *authn.Identity, r return nil } - if !ident.ID.IsType(identity.TypeUser) { + if !ident.ID.IsType(claims.TypeUser) { s.log.FromContext(ctx).Debug("Skip syncing cloud role", "id", ident.ID) return nil } diff --git a/pkg/services/authn/authnimpl/sync/rbac_sync_test.go b/pkg/services/authn/authnimpl/sync/rbac_sync_test.go index 1b0f54ef58e..f38b83679ed 100644 --- a/pkg/services/authn/authnimpl/sync/rbac_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/rbac_sync_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -67,7 +68,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should call sync when authenticated with grafana com and has viewer role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, 1), + ID: identity.NewTypedID(claims.TypeUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, }, @@ -78,7 +79,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should call sync when authenticated with grafana com and has editor role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, 1), + ID: identity.NewTypedID(claims.TypeUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleEditor}, }, @@ -89,7 +90,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should call sync when authenticated with grafana com and has admin role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, 1), + ID: identity.NewTypedID(claims.TypeUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, }, @@ -100,7 +101,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should not call sync when authenticated with grafana com and has invalid role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, 1), + ID: identity.NewTypedID(claims.TypeUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleType("something else")}, }, @@ -111,7 +112,7 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should not call sync when not authenticated with grafana com", module: login.LDAPAuthModule, identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, 1), + ID: identity.NewTypedID(claims.TypeUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, }, @@ -157,7 +158,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) { { desc: "should map Cloud Viewer to Grafana Cloud Viewer and Support ticket reader", identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, 1), + ID: identity.NewTypedID(claims.TypeUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, }, @@ -176,7 +177,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) { { desc: "should map Cloud Editor to Grafana Cloud Editor and Support ticket admin", identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, 1), + ID: identity.NewTypedID(claims.TypeUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleEditor}, }, @@ -194,7 +195,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) { { desc: "should map Cloud Admin to Grafana Cloud Admin and Support ticket admin", identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, 1), + ID: identity.NewTypedID(claims.TypeUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, }, @@ -212,7 +213,7 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) { { desc: "should return an error for not supported role", identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, 1), + ID: identity.NewTypedID(claims.TypeUser, 1), OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleNone}, }, diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index 622846e5ac6..4c00d809b38 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" @@ -118,7 +119,7 @@ func (s *UserSync) FetchSyncedUserHook(ctx context.Context, id *authn.Identity, return nil } - if !id.ID.IsType(identity.TypeUser, identity.TypeServiceAccount) { + if !id.ID.IsType(claims.TypeUser, claims.TypeServiceAccount) { return nil } @@ -159,7 +160,7 @@ func (s *UserSync) SyncLastSeenHook(ctx context.Context, id *authn.Identity, r * return nil } - if !id.ID.IsType(identity.TypeUser, identity.TypeServiceAccount) { + if !id.ID.IsType(claims.TypeUser, claims.TypeServiceAccount) { return nil } @@ -195,7 +196,7 @@ func (s *UserSync) EnableUserHook(ctx context.Context, id *authn.Identity, _ *au return nil } - if !id.ID.IsType(identity.TypeUser) { + if !id.ID.IsType(claims.TypeUser) { return nil } @@ -418,8 +419,8 @@ func (s *UserSync) lookupByOneOf(ctx context.Context, params login.UserLookupPar // syncUserToIdentity syncs a user to an identity. // This is used to update the identity with the latest user information. func syncUserToIdentity(usr *user.User, id *authn.Identity) { - id.ID = identity.NewTypedID(identity.TypeUser, usr.ID) - id.UID = identity.NewTypedIDString(identity.TypeUser, usr.UID) + id.ID = identity.NewTypedID(claims.TypeUser, usr.ID) + id.UID = identity.NewTypedIDString(claims.TypeUser, usr.UID) id.Login = usr.Login id.Email = usr.Email id.Name = usr.Name @@ -429,11 +430,11 @@ func syncUserToIdentity(usr *user.User, id *authn.Identity) { // syncSignedInUserToIdentity syncs a user to an identity. func syncSignedInUserToIdentity(usr *user.SignedInUser, id *authn.Identity) { - var ns identity.IdentityType - if id.ID.IsType(identity.TypeServiceAccount) { - ns = identity.TypeServiceAccount + var ns claims.IdentityType + if id.ID.IsType(claims.TypeServiceAccount) { + ns = claims.TypeServiceAccount } else { - ns = identity.TypeUser + ns = claims.TypeUser } id.UID = identity.NewTypedIDString(ns, usr.UserUID) diff --git a/pkg/services/authn/authnimpl/sync/user_sync_test.go b/pkg/services/authn/authnimpl/sync/user_sync_test.go index 3363f421662..82d76e729aa 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/user_sync_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/authn" @@ -484,7 +485,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) { { desc: "should skip if correct flag is not set", identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, 1), + ID: identity.NewTypedID(claims.TypeUser, 1), IsDisabled: true, ClientParams: authn.ClientParams{EnableUser: false}, }, @@ -493,7 +494,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) { { desc: "should skip if identity is not a user", identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeAPIKey, 1), + ID: identity.NewTypedID(claims.TypeAPIKey, 1), IsDisabled: true, ClientParams: authn.ClientParams{EnableUser: true}, }, @@ -502,7 +503,7 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) { { desc: "should enabled disabled user", identity: &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, 1), + ID: identity.NewTypedID(claims.TypeUser, 1), IsDisabled: true, ClientParams: authn.ClientParams{EnableUser: true}, }, diff --git a/pkg/services/authn/authntest/mock.go b/pkg/services/authn/authntest/mock.go index a4934ca640b..81dad579b61 100644 --- a/pkg/services/authn/authntest/mock.go +++ b/pkg/services/authn/authntest/mock.go @@ -3,6 +3,7 @@ package authntest import ( "context" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/models/usertoken" "github.com/grafana/grafana/pkg/services/authn" @@ -77,7 +78,7 @@ type MockClient struct { PriorityFunc func() uint HookFunc func(ctx context.Context, identity *authn.Identity, r *authn.Request) error LogoutFunc func(ctx context.Context, user identity.Requester) (*authn.Redirect, bool) - IdentityTypeFunc func() identity.IdentityType + IdentityTypeFunc func() claims.IdentityType ResolveIdentityFunc func(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) } @@ -127,11 +128,11 @@ func (m *MockClient) Logout(ctx context.Context, user identity.Requester) (*auth return nil, false } -func (m *MockClient) IdentityType() identity.IdentityType { +func (m *MockClient) IdentityType() claims.IdentityType { if m.IdentityTypeFunc != nil { return m.IdentityTypeFunc() } - return identity.TypeEmpty + return claims.TypeEmpty } // ResolveIdentity implements authn.IdentityResolverClient. diff --git a/pkg/services/authn/clients/api_key.go b/pkg/services/authn/clients/api_key.go index bfbe8b59bd1..fdab410c979 100644 --- a/pkg/services/authn/clients/api_key.go +++ b/pkg/services/authn/clients/api_key.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/components/apikeygen" @@ -135,12 +136,12 @@ func (s *APIKey) Priority() uint { return 30 } -func (s *APIKey) IdentityType() identity.IdentityType { - return identity.TypeAPIKey +func (s *APIKey) IdentityType() claims.IdentityType { + return claims.TypeAPIKey } func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) { - if !namespaceID.IsType(identity.TypeAPIKey) { + if !namespaceID.IsType(claims.TypeAPIKey) { return nil, identity.ErrInvalidTypedID.Errorf("got unspected namespace: %s", namespaceID.Type()) } @@ -195,11 +196,11 @@ func (s *APIKey) getAPIKeyID(ctx context.Context, id *authn.Identity, r *authn.R return -1, false } - if id.ID.IsType(identity.TypeAPIKey) { + if id.ID.IsType(claims.TypeAPIKey) { return internalId, true } - if id.ID.IsType(identity.TypeServiceAccount) { + if id.ID.IsType(claims.TypeServiceAccount) { // When the identity is service account, the ID in from the namespace is the service account ID. // We need to fetch the API key in this scenario, as we could use it to uniquely identify a service account token. apiKey, err := s.getAPIKey(ctx, getTokenFromRequest(r)) @@ -256,7 +257,7 @@ func validateApiKey(orgID int64, key *apikey.APIKey) error { func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity { return &authn.Identity{ - ID: identity.NewTypedID(identity.TypeAPIKey, key.ID), + ID: identity.NewTypedID(claims.TypeAPIKey, key.ID), OrgID: key.OrgID, OrgRoles: map[int64]org.RoleType{key.OrgID: key.Role}, ClientParams: authn.ClientParams{SyncPermissions: true}, @@ -266,7 +267,7 @@ func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity { func newServiceAccountIdentity(key *apikey.APIKey) *authn.Identity { return &authn.Identity{ - ID: identity.NewTypedID(identity.TypeServiceAccount, *key.ServiceAccountId), + ID: identity.NewTypedID(claims.TypeServiceAccount, *key.ServiceAccountId), OrgID: key.OrgID, AuthenticatedBy: login.APIKeyAuthModule, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, diff --git a/pkg/services/authn/clients/ext_jwt.go b/pkg/services/authn/clients/ext_jwt.go index af0682b8bd9..f44d1705d6b 100644 --- a/pkg/services/authn/clients/ext_jwt.go +++ b/pkg/services/authn/clients/ext_jwt.go @@ -8,7 +8,7 @@ import ( "github.com/go-jose/go-jose/v3/jwt" authlib "github.com/grafana/authlib/authn" - + authlibclaims "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" @@ -114,7 +114,7 @@ func (s *ExtendedJWT) authenticateAsUser( return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessID.String()) } - if !accessID.IsType(identity.TypeAccessPolicy) { + if !accessID.IsType(authlibclaims.TypeAccessPolicy) { return nil, errExtJWTInvalid.Errorf("unexpected identity: %s", accessID.String()) } @@ -123,7 +123,7 @@ func (s *ExtendedJWT) authenticateAsUser( return nil, errExtJWTInvalid.Errorf("failed to parse id token subject: %w", err) } - if !userID.IsType(identity.TypeUser) { + if !userID.IsType(authlibclaims.TypeUser) { return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", userID.String()) } @@ -160,7 +160,7 @@ func (s *ExtendedJWT) authenticateAsService(claims *authlib.Claims[authlib.Acces return nil, fmt.Errorf("failed to parse access token subject: %w", err) } - if !id.IsType(identity.TypeAccessPolicy) { + if !id.IsType(authlibclaims.TypeAccessPolicy) { return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", id.String()) } diff --git a/pkg/services/authn/clients/ext_jwt_test.go b/pkg/services/authn/clients/ext_jwt_test.go index de612aecf4c..e87feec4c61 100644 --- a/pkg/services/authn/clients/ext_jwt_test.go +++ b/pkg/services/authn/clients/ext_jwt_test.go @@ -11,15 +11,12 @@ import ( "github.com/go-jose/go-jose/v3" "github.com/go-jose/go-jose/v3/jwt" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - authnlib "github.com/grafana/authlib/authn" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/setting" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type ( diff --git a/pkg/services/authn/clients/grafana.go b/pkg/services/authn/clients/grafana.go index 32ce5725a08..04de6c88151 100644 --- a/pkg/services/authn/clients/grafana.go +++ b/pkg/services/authn/clients/grafana.go @@ -6,6 +6,7 @@ import ( "errors" "net/mail" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" @@ -106,7 +107,7 @@ func (c *Grafana) AuthenticatePassword(ctx context.Context, r *authn.Request, us } return &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, usr.ID), + ID: identity.NewTypedID(claims.TypeUser, usr.ID), OrgID: r.OrgID, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, AuthenticatedBy: login.PasswordAuthModule, diff --git a/pkg/services/authn/clients/oauth_test.go b/pkg/services/authn/clients/oauth_test.go index c23c2c348d9..9ae0de62a3a 100644 --- a/pkg/services/authn/clients/oauth_test.go +++ b/pkg/services/authn/clients/oauth_test.go @@ -14,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/login/social" "github.com/grafana/grafana/pkg/login/social/socialtest" @@ -485,7 +486,7 @@ func TestOAuth_Logout(t *testing.T) { } c := ProvideOAuth(authn.ClientWithPrefix("azuread"), tt.cfg, mockService, fakeSocialSvc, &setting.OSSImpl{Cfg: tt.cfg}, featuremgmt.WithFeatures()) - redirect, ok := c.Logout(context.Background(), &authn.Identity{ID: identity.NewTypedIDString(identity.TypeUser, "1")}) + redirect, ok := c.Logout(context.Background(), &authn.Identity{ID: identity.NewTypedIDString(claims.TypeUser, "1")}) assert.Equal(t, tt.expectedOK, ok) if tt.expectedOK { diff --git a/pkg/services/authn/clients/proxy.go b/pkg/services/authn/clients/proxy.go index 9f853ccc889..7c838d119ef 100644 --- a/pkg/services/authn/clients/proxy.go +++ b/pkg/services/authn/clients/proxy.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" @@ -125,7 +126,7 @@ func (c *Proxy) retrieveIDFromCache(ctx context.Context, cacheKey string, r *aut } return &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, uid), + ID: identity.NewTypedID(claims.TypeUser, uid), OrgID: r.OrgID, // FIXME: This does not match the actual auth module used, but should not have any impact // Maybe caching the auth module used with the user ID would be a good idea @@ -150,7 +151,7 @@ func (c *Proxy) Hook(ctx context.Context, id *authn.Identity, r *authn.Request) return nil } - if !id.ID.IsType(identity.TypeUser) { + if !id.ID.IsType(claims.TypeUser) { return nil } diff --git a/pkg/services/authn/clients/proxy_test.go b/pkg/services/authn/clients/proxy_test.go index 59bec50fbaf..7aa39efaa71 100644 --- a/pkg/services/authn/clients/proxy_test.go +++ b/pkg/services/authn/clients/proxy_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn/authntest" @@ -204,7 +205,7 @@ func TestProxy_Hook(t *testing.T) { } cache := &fakeCache{data: make(map[string][]byte)} userId := int64(1) - userID := identity.NewTypedID(identity.TypeUser, userId) + userID := identity.NewTypedID(claims.TypeUser, userId) // withRole creates a test case for a user with a specific role. withRole := func(role string) func(t *testing.T) { diff --git a/pkg/services/authn/clients/render.go b/pkg/services/authn/clients/render.go index 9491d946e39..6ed57114af0 100644 --- a/pkg/services/authn/clients/render.go +++ b/pkg/services/authn/clients/render.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/authn" @@ -43,7 +44,7 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide if renderUsr.UserID <= 0 { return &authn.Identity{ - ID: identity.NewTypedID(identity.TypeRenderService, 0), + ID: identity.NewTypedID(claims.TypeRenderService, 0), OrgID: renderUsr.OrgID, OrgRoles: map[int64]org.RoleType{renderUsr.OrgID: org.RoleType(renderUsr.OrgRole)}, ClientParams: authn.ClientParams{SyncPermissions: true}, @@ -53,7 +54,7 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide } return &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, renderUsr.UserID), + ID: identity.NewTypedID(claims.TypeUser, renderUsr.UserID), LastSeenAt: time.Now(), AuthenticatedBy: login.RenderModule, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, diff --git a/pkg/services/authn/clients/session.go b/pkg/services/authn/clients/session.go index 3527d372d98..9945612c79f 100644 --- a/pkg/services/authn/clients/session.go +++ b/pkg/services/authn/clients/session.go @@ -6,6 +6,7 @@ import ( "net/url" "time" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/auth" @@ -58,7 +59,7 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id } ident := &authn.Identity{ - ID: identity.NewTypedID(identity.TypeUser, token.UserId), + ID: identity.NewTypedID(claims.TypeUser, token.UserId), SessionToken: token, ClientParams: authn.ClientParams{ FetchSyncedUser: true, diff --git a/pkg/services/authn/identity.go b/pkg/services/authn/identity.go index a1b9da18c62..aa16ebb636f 100644 --- a/pkg/services/authn/identity.go +++ b/pkg/services/authn/identity.go @@ -99,7 +99,7 @@ func (i *Identity) GetInternalID() (int64, error) { } // GetIdentityType implements Requester. -func (i *Identity) GetIdentityType() identity.IdentityType { +func (i *Identity) GetIdentityType() claims.IdentityType { return i.UID.Type() } @@ -243,9 +243,9 @@ func (i *Identity) HasRole(role org.RoleType) bool { func (i *Identity) HasUniqueId() bool { typ := i.GetID().Type() - return typ == identity.TypeUser || - typ == identity.TypeServiceAccount || - typ == identity.TypeAPIKey + return typ == claims.TypeUser || + typ == claims.TypeServiceAccount || + typ == claims.TypeAPIKey } func (i *Identity) IsAuthenticatedBy(providers ...string) bool { @@ -273,7 +273,7 @@ func (i *Identity) SignedInUser() *user.SignedInUser { AuthID: i.AuthID, AuthenticatedBy: i.AuthenticatedBy, IsGrafanaAdmin: i.GetIsGrafanaAdmin(), - IsAnonymous: i.ID.IsType(identity.TypeAnonymous), + IsAnonymous: i.ID.IsType(claims.TypeAnonymous), IsDisabled: i.IsDisabled, HelpFlags1: i.HelpFlags1, LastSeenAt: i.LastSeenAt, @@ -283,14 +283,14 @@ func (i *Identity) SignedInUser() *user.SignedInUser { FallbackType: i.ID.Type(), } - if i.ID.IsType(identity.TypeAPIKey) { + if i.ID.IsType(claims.TypeAPIKey) { id, _ := i.ID.ParseInt() u.ApiKeyID = id } else { id, _ := i.ID.UserID() u.UserID = id u.UserUID = i.UID.ID() - u.IsServiceAccount = i.ID.IsType(identity.TypeServiceAccount) + u.IsServiceAccount = i.ID.IsType(claims.TypeServiceAccount) } return u diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index 24ac584a9b8..d5ea06013fa 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -9,6 +9,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" + "github.com/grafana/authlib/claims" authnClients "github.com/grafana/grafana/pkg/services/authn/clients" "github.com/grafana/grafana/pkg/api/response" @@ -157,9 +158,9 @@ func (h *ContextHandler) addIDHeaderEndOfRequestFunc(ident identity.Requester) w id := ident.GetID() if !identity.IsIdentityType( id, - identity.TypeUser, - identity.TypeServiceAccount, - identity.TypeAPIKey, + claims.TypeUser, + claims.TypeServiceAccount, + claims.TypeAPIKey, ) || id.ID() == "0" { return } diff --git a/pkg/services/contexthandler/contexthandler_test.go b/pkg/services/contexthandler/contexthandler_test.go index ae8807ad209..27f3e1cd03c 100644 --- a/pkg/services/contexthandler/contexthandler_test.go +++ b/pkg/services/contexthandler/contexthandler_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/tracing" @@ -43,7 +44,7 @@ func TestContextHandler(t *testing.T) { }) t.Run("should set identity on successful authentication", func(t *testing.T) { - id := &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, 1), OrgID: 1} + id := &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1), OrgID: 1} handler := contexthandler.ProvideService( setting.NewCfg(), tracing.InitializeTracerForTest(), diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 17b9d752c5e..4b85d539a8a 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" @@ -879,7 +879,7 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F } // only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users - if query.SignedInUser == nil || query.SignedInUser.GetID().Type() != identity.TypeServiceAccount { + if query.SignedInUser == nil || query.SignedInUser.GetID().Type() != claims.TypeServiceAccount { filters = append(filters, searchstore.K6FolderFilter{}) } diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index 7d70832a8d6..d3dc27e2b28 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" "github.com/prometheus/client_golang/prometheus" "golang.org/x/exp/slices" @@ -492,7 +493,7 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto * userID, err := identity.IntIdentifier(dto.User.GetID()) if err != nil { dr.log.Error("Could not make user admin", "dashboard", dash.Title, "id", dto.User.GetID(), "error", err) - } else if identity.IsIdentityType(dto.User.GetID(), identity.TypeUser) { + } else if identity.IsIdentityType(dto.User.GetID(), claims.TypeUser) { permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(), }) @@ -528,7 +529,7 @@ func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, userID, err := identity.IntIdentifier(cmd.SignedInUser.GetID()) if err != nil { dr.log.Error("Could not make user admin", "folder", cmd.Title, "id", cmd.SignedInUser.GetID()) - } else if identity.IsIdentityType(cmd.SignedInUser.GetID(), identity.TypeUser) { + } else if identity.IsIdentityType(cmd.SignedInUser.GetID(), claims.TypeUser) { permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(), }) diff --git a/pkg/services/dashboardsnapshots/database/database.go b/pkg/services/dashboardsnapshots/database/database.go index a040133e43a..bcc7c9c32e8 100644 --- a/pkg/services/dashboardsnapshots/database/database.go +++ b/pkg/services/dashboardsnapshots/database/database.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" @@ -123,7 +124,7 @@ func (d *DashboardSnapshotStore) SearchDashboardSnapshots(ctx context.Context, q } var userID int64 - if identity.IsIdentityType(query.SignedInUser.GetID(), identity.TypeUser, identity.TypeServiceAccount) { + if identity.IsIdentityType(query.SignedInUser.GetID(), claims.TypeUser, claims.TypeServiceAccount) { var err error userID, err = identity.UserIdentifier(query.SignedInUser.GetID()) if err != nil { diff --git a/pkg/services/folder/folderimpl/sqlstore.go b/pkg/services/folder/folderimpl/sqlstore.go index d5c1e1c54af..d2b0c7a2bc3 100644 --- a/pkg/services/folder/folderimpl/sqlstore.go +++ b/pkg/services/folder/folderimpl/sqlstore.go @@ -7,9 +7,9 @@ import ( "strings" "time" + "github.com/grafana/authlib/claims" "github.com/grafana/dskit/concurrency" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" @@ -323,7 +323,7 @@ func (ss *sqlStore) GetChildren(ctx context.Context, q folder.GetChildrenQuery) } // only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users - if q.SignedInUser == nil || q.SignedInUser.GetID().Type() != identity.TypeServiceAccount { + if q.SignedInUser == nil || q.SignedInUser.GetID().Type() != claims.TypeServiceAccount { sql.WriteString(" AND uid != ?") args = append(args, accesscontrol.K6FolderUID) } @@ -484,7 +484,7 @@ func (ss *sqlStore) GetFolders(ctx context.Context, q getFoldersQuery) ([]*folde } // only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users - if q.SignedInUser == nil || q.SignedInUser.GetID().Type() != identity.TypeServiceAccount { + if q.SignedInUser == nil || q.SignedInUser.GetID().Type() != claims.TypeServiceAccount { s.WriteString(" AND f0.uid != ? AND (f0.parent_uid != ? OR f0.parent_uid IS NULL)") args = append(args, accesscontrol.K6FolderUID, accesscontrol.K6FolderUID) } diff --git a/pkg/services/oauthtoken/oauth_token.go b/pkg/services/oauthtoken/oauth_token.go index fa351fddeae..cc736d959b4 100644 --- a/pkg/services/oauthtoken/oauth_token.go +++ b/pkg/services/oauthtoken/oauth_token.go @@ -12,6 +12,7 @@ import ( "golang.org/x/oauth2" "golang.org/x/sync/singleflight" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" @@ -94,7 +95,7 @@ func (o *Service) HasOAuthEntry(ctx context.Context, usr identity.Requester) (*l return nil, false, nil } - if !identity.IsIdentityType(usr.GetID(), identity.TypeUser) { + if !identity.IsIdentityType(usr.GetID(), claims.TypeUser) { return nil, false, nil } @@ -135,7 +136,7 @@ func (o *Service) TryTokenRefresh(ctx context.Context, usr identity.Requester) e return nil } - if !identity.IsIdentityType(usr.GetID(), identity.TypeUser) { + if !identity.IsIdentityType(usr.GetID(), claims.TypeUser) { ctxLogger.Warn("Can only refresh OAuth tokens for users", "id", usr.GetID()) return nil } diff --git a/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go b/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go index 6caebc23859..e15ae29e7db 100644 --- a/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go +++ b/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go @@ -3,6 +3,7 @@ package clientmiddleware import ( "context" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -35,7 +36,7 @@ func (m *UserHeaderMiddleware) applyUserHeader(ctx context.Context, h backend.Fo } h.DeleteHTTPHeader(proxyutil.UserHeaderName) - if !identity.IsIdentityType(reqCtx.SignedInUser.GetID(), identity.TypeAnonymous) { + if !identity.IsIdentityType(reqCtx.SignedInUser.GetID(), claims.TypeAnonymous) { h.SetHTTPHeader(proxyutil.UserHeaderName, reqCtx.SignedInUser.GetLogin()) } } diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go index 7104dbd06ff..a14f05993bd 100644 --- a/pkg/services/serviceaccounts/api/api.go +++ b/pkg/services/serviceaccounts/api/api.go @@ -4,6 +4,7 @@ import ( "net/http" "strconv" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" @@ -99,7 +100,7 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *contextmodel.ReqContext) } if api.cfg.RBAC.PermissionsOnCreation("service-account") { - if identity.IsIdentityType(c.SignedInUser.GetID(), identity.TypeUser) { + if identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { userID, err := c.SignedInUser.GetID().ParseInt() if err != nil { return response.Error(http.StatusInternalServerError, "Failed to parse user id", err) diff --git a/pkg/services/team/teamapi/team.go b/pkg/services/team/teamapi/team.go index 1b83bb49ebb..0fc23039fc0 100644 --- a/pkg/services/team/teamapi/team.go +++ b/pkg/services/team/teamapi/team.go @@ -5,9 +5,9 @@ import ( "net/http" "strconv" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/accesscontrol" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" @@ -51,7 +51,7 @@ func (tapi *TeamAPI) createTeam(c *contextmodel.ReqContext) response.Response { // an additional check whether it is an actual user is required namespace, identifier := c.SignedInUser.GetID().Type(), c.SignedInUser.GetID().ID() switch namespace { - case identity.TypeUser: + case claims.TypeUser: userID, err := strconv.ParseInt(identifier, 10, 64) if err != nil { c.Logger.Error("Could not add creator to team because user id is not a number", "error", err) diff --git a/pkg/services/user/identity.go b/pkg/services/user/identity.go index 37671cd2fe3..e9b0a4c2011 100644 --- a/pkg/services/user/identity.go +++ b/pkg/services/user/identity.go @@ -50,7 +50,7 @@ type SignedInUser struct { IDTokenClaims *authnlib.Claims[authnlib.IDTokenClaims] `json:"-" xorm:"-"` // When other settings are not deterministic, this value is used - FallbackType identity.IdentityType + FallbackType claims.IdentityType } // Access implements claims.AuthInfo. @@ -89,18 +89,18 @@ func (u *SignedInUser) GetInternalID() (int64, error) { } // GetIdentityType implements Requester. -func (u *SignedInUser) GetIdentityType() identity.IdentityType { +func (u *SignedInUser) GetIdentityType() claims.IdentityType { switch { case u.ApiKeyID != 0: - return identity.TypeAPIKey + return claims.TypeAPIKey case u.IsServiceAccount: - return identity.TypeServiceAccount + return claims.TypeServiceAccount case u.UserID > 0: - return identity.TypeUser + return claims.TypeUser case u.IsAnonymous: - return identity.TypeAnonymous + return claims.TypeAnonymous case u.AuthenticatedBy == "render" && u.UserID == 0: - return identity.TypeRenderService + return claims.TypeRenderService } return u.FallbackType } @@ -263,18 +263,18 @@ func (u *SignedInUser) GetID() identity.TypedID { return identity.NewTypedIDString(ns, id) } -func (u *SignedInUser) getTypeAndID() (identity.IdentityType, string) { +func (u *SignedInUser) getTypeAndID() (claims.IdentityType, string) { switch { case u.ApiKeyID != 0: - return identity.TypeAPIKey, strconv.FormatInt(u.ApiKeyID, 10) + return claims.TypeAPIKey, strconv.FormatInt(u.ApiKeyID, 10) case u.IsServiceAccount: - return identity.TypeServiceAccount, strconv.FormatInt(u.UserID, 10) + return claims.TypeServiceAccount, strconv.FormatInt(u.UserID, 10) case u.UserID > 0: - return identity.TypeUser, strconv.FormatInt(u.UserID, 10) + return claims.TypeUser, strconv.FormatInt(u.UserID, 10) case u.IsAnonymous: - return identity.TypeAnonymous, "0" + return claims.TypeAnonymous, "0" case u.AuthenticatedBy == "render" && u.UserID == 0: - return identity.TypeRenderService, "0" + return claims.TypeRenderService, "0" } return u.FallbackType, strconv.FormatInt(u.UserID, 10) diff --git a/pkg/services/user/userimpl/verifier.go b/pkg/services/user/userimpl/verifier.go index 4bbb9b6ea3c..1569ba46285 100644 --- a/pkg/services/user/userimpl/verifier.go +++ b/pkg/services/user/userimpl/verifier.go @@ -7,6 +7,7 @@ import ( "net/mail" "time" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/auth" @@ -153,6 +154,6 @@ func (s *Verifier) Complete(ctx context.Context, cmd user.CompleteEmailVerifyCom // remove the current token, so a new one can be generated with correct values. return s.is.RemoveIDToken( ctx, - &authn.Identity{ID: identity.NewTypedID(identity.TypeUser, usr.ID), OrgID: usr.OrgID}, + &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, usr.ID), OrgID: usr.OrgID}, ) } diff --git a/pkg/storage/unified/apistore/store_test.go b/pkg/storage/unified/apistore/store_test.go index 3ac93500675..8977693c966 100644 --- a/pkg/storage/unified/apistore/store_test.go +++ b/pkg/storage/unified/apistore/store_test.go @@ -20,6 +20,7 @@ import ( examplev1 "k8s.io/apiserver/pkg/apis/example/v1" "k8s.io/apiserver/pkg/storage" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" storagetesting "github.com/grafana/grafana/pkg/apiserver/storage/testing" ) @@ -32,7 +33,7 @@ func init() { // Make sure there is a user in every context storagetesting.NewContext = func() context.Context { testUserA := &identity.StaticRequester{ - Type: identity.TypeUser, + Type: claims.TypeUser, Login: "testuser", UserID: 123, UserUID: "u123", diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 2ce0faad857..885dc1de9ae 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -5,6 +5,7 @@ go 1.22.4 require ( github.com/fullstorydev/grpchan v1.1.1 github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1 + github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808164224-787abccfbc9e github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 github.com/prometheus/client_golang v1.19.1 diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 602360e405f..fe078d7593d 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -349,6 +349,8 @@ github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBH github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1 h1:EiaupmOnt6XF/LPxvagjTofWmByzYaf5VyMIF+w/71M= github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1/go.mod h1:YA9We4kTafu7mlMnUh3In6Q2wpg8fYN3ycgCKOK1TB8= +github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 h1:uD1LcKwvEAqzDsgVChBudPqo5BhPxkj9AgylT5QCReo= +github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808164224-787abccfbc9e h1:3vNpomyzv714Hgls5vn+fC0vgv8wUOSHepUl7PB5nUs= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808164224-787abccfbc9e/go.mod h1:ORVFiW/KNRY52lNjkGwnFWCxNVfE97bJG2jr2fetq0I= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= diff --git a/pkg/storage/unified/resource/grpc/authenticator.go b/pkg/storage/unified/resource/grpc/authenticator.go index ad0efe0a46f..23322c998af 100644 --- a/pkg/storage/unified/resource/grpc/authenticator.go +++ b/pkg/storage/unified/resource/grpc/authenticator.go @@ -6,6 +6,7 @@ import ( "strconv" "github.com/grafana/authlib/authn" + "github.com/grafana/authlib/claims" "google.golang.org/grpc" "google.golang.org/grpc/metadata" @@ -76,7 +77,7 @@ func (f *Authenticator) decodeMetadata(ctx context.Context, meta metadata.MD) (i // TODO, remove after this has been deployed to unified storage if getter(mdUserID) == "" { var err error - user.Type = identity.TypeUser + user.Type = claims.TypeUser user.UserID, err = strconv.ParseInt(getter("grafana-userid"), 10, 64) if err != nil { return nil, fmt.Errorf("invalid user id: %w", err) diff --git a/pkg/storage/unified/resource/grpc/authenticator_test.go b/pkg/storage/unified/resource/grpc/authenticator_test.go index 40279763b1e..d44268a0232 100644 --- a/pkg/storage/unified/resource/grpc/authenticator_test.go +++ b/pkg/storage/unified/resource/grpc/authenticator_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/require" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" ) @@ -14,7 +15,7 @@ func TestBasicEncodeDecode(t *testing.T) { UserID: 123, UserUID: "abc", Login: "test", - Type: identity.TypeUser, + Type: claims.TypeUser, OrgID: 456, OrgName: "org", OrgRole: identity.RoleAdmin, diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index ac2c02cb947..e3dc65dc696 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -15,6 +15,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" ) @@ -122,7 +123,7 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) { // Make this cancelable ctx, cancel := context.WithCancel(identity.WithRequester(context.Background(), &identity.StaticRequester{ - Type: identity.TypeServiceAccount, + Type: claims.TypeServiceAccount, Login: "watcher", // admin user for watch UserID: 1, IsGrafanaAdmin: true, diff --git a/pkg/storage/unified/resource/server_test.go b/pkg/storage/unified/resource/server_test.go index 63023aa3d34..30a81554c68 100644 --- a/pkg/storage/unified/resource/server_test.go +++ b/pkg/storage/unified/resource/server_test.go @@ -13,13 +13,14 @@ import ( "gocloud.dev/blob/memblob" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/apimachinery/utils" ) func TestSimpleServer(t *testing.T) { testUserA := &identity.StaticRequester{ - Type: identity.TypeUser, + Type: claims.TypeUser, Login: "testuser", UserID: 123, UserUID: "u123", diff --git a/pkg/storage/unified/sql/test/integration_test.go b/pkg/storage/unified/sql/test/integration_test.go index 1636de069ad..f519480d061 100644 --- a/pkg/storage/unified/sql/test/integration_test.go +++ b/pkg/storage/unified/sql/test/integration_test.go @@ -10,6 +10,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "github.com/grafana/authlib/claims" "github.com/grafana/dskit/services" "github.com/grafana/grafana/pkg/apimachinery/identity" infraDB "github.com/grafana/grafana/pkg/infra/db" @@ -66,7 +67,7 @@ func TestIntegrationBackendHappyPath(t *testing.T) { } testUserA := &identity.StaticRequester{ - Type: identity.TypeUser, + Type: claims.TypeUser, Login: "testuser", UserID: 123, UserUID: "u123", @@ -341,7 +342,7 @@ func TestClientServer(t *testing.T) { // Test with an admin identity clientCtx := identity.WithRequester(ctx, &identity.StaticRequester{ - Type: identity.TypeUser, + Type: claims.TypeUser, Login: "testuser", UserID: 123, UserUID: "u123", diff --git a/pkg/util/proxyutil/proxyutil.go b/pkg/util/proxyutil/proxyutil.go index f53bff01146..4323d7c3f1b 100644 --- a/pkg/util/proxyutil/proxyutil.go +++ b/pkg/util/proxyutil/proxyutil.go @@ -7,6 +7,7 @@ import ( "sort" "strings" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" ) @@ -114,7 +115,7 @@ func ApplyUserHeader(sendUserHeader bool, req *http.Request, user identity.Reque return } - if identity.IsIdentityType(user.GetID(), identity.TypeUser) { + if identity.IsIdentityType(user.GetID(), claims.TypeUser) { req.Header.Set(UserHeaderName, user.GetLogin()) } } diff --git a/pkg/util/proxyutil/proxyutil_test.go b/pkg/util/proxyutil/proxyutil_test.go index 40836a75d40..4c89cc9bb19 100644 --- a/pkg/util/proxyutil/proxyutil_test.go +++ b/pkg/util/proxyutil/proxyutil_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/services/user" ) @@ -175,7 +175,7 @@ func TestApplyUserHeader(t *testing.T) { require.NoError(t, err) req.Header.Set("X-Grafana-User", "admin") - ApplyUserHeader(false, req, &user.SignedInUser{Login: "admin", UserID: 1, FallbackType: identity.TypeUser}) + ApplyUserHeader(false, req, &user.SignedInUser{Login: "admin", UserID: 1, FallbackType: claims.TypeUser}) require.NotContains(t, req.Header, "X-Grafana-User") }) @@ -192,7 +192,7 @@ func TestApplyUserHeader(t *testing.T) { req, err := http.NewRequest(http.MethodGet, "/", nil) require.NoError(t, err) - ApplyUserHeader(true, req, &user.SignedInUser{IsAnonymous: true, FallbackType: identity.TypeAnonymous}) + ApplyUserHeader(true, req, &user.SignedInUser{IsAnonymous: true, FallbackType: claims.TypeAnonymous}) require.NotContains(t, req.Header, "X-Grafana-User") }) @@ -200,7 +200,7 @@ func TestApplyUserHeader(t *testing.T) { req, err := http.NewRequest(http.MethodGet, "/", nil) require.NoError(t, err) - ApplyUserHeader(true, req, &user.SignedInUser{Login: "admin", UserID: 1, FallbackType: identity.TypeUser}) + ApplyUserHeader(true, req, &user.SignedInUser{Login: "admin", UserID: 1, FallbackType: claims.TypeUser}) require.Equal(t, "admin", req.Header.Get("X-Grafana-User")) }) } From e106df6d0bd2f00ccce11b9500f5973cd201dfa5 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Mon, 12 Aug 2024 09:49:34 +0300 Subject: [PATCH 007/229] Auth: Update unified storage to depend on AuthInfo rather than Requester (#91783) depend on authlib user info --- pkg/storage/unified/resource/hooks.go | 10 +++++----- pkg/storage/unified/resource/server.go | 22 +++++++++++---------- pkg/storage/unified/resource/server_test.go | 9 ++++----- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/pkg/storage/unified/resource/hooks.go b/pkg/storage/unified/resource/hooks.go index a1163951f60..605700791f9 100644 --- a/pkg/storage/unified/resource/hooks.go +++ b/pkg/storage/unified/resource/hooks.go @@ -4,16 +4,16 @@ import ( context "context" "fmt" - "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/authlib/claims" ) type WriteAccessHooks struct { // Check if a user has access to write folders // When this is nil, no resources can have folders configured - Folder func(ctx context.Context, user identity.Requester, uid string) bool + Folder func(ctx context.Context, user claims.AuthInfo, uid string) bool // When configured, this will make sure a user is allowed to save to a given origin - Origin func(ctx context.Context, user identity.Requester, origin string) bool + Origin func(ctx context.Context, user claims.AuthInfo, origin string) bool } type LifecycleHooks interface { @@ -24,7 +24,7 @@ type LifecycleHooks interface { Stop(context.Context) error } -func (a *WriteAccessHooks) CanWriteFolder(ctx context.Context, user identity.Requester, uid string) error { +func (a *WriteAccessHooks) CanWriteFolder(ctx context.Context, user claims.AuthInfo, uid string) error { if a.Folder == nil { return fmt.Errorf("writing folders is not supported") } @@ -34,7 +34,7 @@ func (a *WriteAccessHooks) CanWriteFolder(ctx context.Context, user identity.Req return nil } -func (a *WriteAccessHooks) CanWriteOrigin(ctx context.Context, user identity.Requester, uid string) error { +func (a *WriteAccessHooks) CanWriteOrigin(ctx context.Context, user claims.AuthInfo, uid string) error { if a.Origin == nil || uid == "UI" { return nil // default to OK } diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go index e3dc65dc696..3b401bb1b5f 100644 --- a/pkg/storage/unified/resource/server.go +++ b/pkg/storage/unified/resource/server.go @@ -121,7 +121,7 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) { } // Make this cancelable - ctx, cancel := context.WithCancel(identity.WithRequester(context.Background(), + ctx, cancel := context.WithCancel(claims.WithClaims(context.Background(), &identity.StaticRequester{ Type: claims.TypeServiceAccount, Login: "watcher", // admin user for watch @@ -212,7 +212,7 @@ func (s *server) Stop(ctx context.Context) error { } // Old value indicates an update -- otherwise a create -func (s *server) newEvent(ctx context.Context, user identity.Requester, key *ResourceKey, value, oldValue []byte) (*WriteEvent, *ErrorResult) { +func (s *server) newEvent(ctx context.Context, user claims.AuthInfo, key *ResourceKey, value, oldValue []byte) (*WriteEvent, *ErrorResult) { tmp := &unstructured.Unstructured{} err := tmp.UnmarshalJSON(value) if err != nil { @@ -304,8 +304,8 @@ func (s *server) Create(ctx context.Context, req *CreateRequest) (*CreateRespons } rsp := &CreateResponse{} - user, err := identity.GetRequester(ctx) - if err != nil || user == nil { + user, ok := claims.From(ctx) + if !ok || user == nil { rsp.Error = &ErrorResult{ Message: "no user found in context", Code: http.StatusUnauthorized, @@ -328,6 +328,7 @@ func (s *server) Create(ctx context.Context, req *CreateRequest) (*CreateRespons return rsp, nil } + var err error rsp.ResourceVersion, err = s.backend.WriteEvent(ctx, *event) if err != nil { rsp.Error = AsErrorResult(err) @@ -344,8 +345,8 @@ func (s *server) Update(ctx context.Context, req *UpdateRequest) (*UpdateRespons } rsp := &UpdateResponse{} - user, err := identity.GetRequester(ctx) - if err != nil || user == nil { + user, ok := claims.From(ctx) + if !ok || user == nil { rsp.Error = &ErrorResult{ Message: "no user found in context", Code: http.StatusUnauthorized, @@ -375,12 +376,13 @@ func (s *server) Update(ctx context.Context, req *UpdateRequest) (*UpdateRespons event, e := s.newEvent(ctx, user, req.Key, req.Value, latest.Value) if e != nil { rsp.Error = e - return rsp, err + return rsp, nil } event.Type = WatchEvent_MODIFIED event.PreviousRV = latest.ResourceVersion + var err error rsp.ResourceVersion, err = s.backend.WriteEvent(ctx, *event) if err != nil { rsp.Error = AsErrorResult(err) @@ -419,12 +421,12 @@ func (s *server) Delete(ctx context.Context, req *DeleteRequest) (*DeleteRespons Type: WatchEvent_DELETED, PreviousRV: latest.ResourceVersion, } - requester, err := identity.GetRequester(ctx) - if err != nil { + requester, ok := claims.From(ctx) + if !ok { return nil, apierrors.NewBadRequest("unable to get user") } marker := &DeletedMarker{} - err = json.Unmarshal(latest.Value, marker) + err := json.Unmarshal(latest.Value, marker) if err != nil { return nil, apierrors.NewBadRequest( fmt.Sprintf("unable to read previous object, %v", err)) diff --git a/pkg/storage/unified/resource/server_test.go b/pkg/storage/unified/resource/server_test.go index 30a81554c68..d6d935f5609 100644 --- a/pkg/storage/unified/resource/server_test.go +++ b/pkg/storage/unified/resource/server_test.go @@ -8,14 +8,13 @@ import ( "testing" "time" + "github.com/grafana/authlib/claims" + "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/stretchr/testify/require" "gocloud.dev/blob/fileblob" "gocloud.dev/blob/memblob" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - - "github.com/grafana/authlib/claims" - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/apimachinery/utils" ) func TestSimpleServer(t *testing.T) { @@ -27,7 +26,7 @@ func TestSimpleServer(t *testing.T) { OrgRole: identity.RoleAdmin, IsGrafanaAdmin: true, // can do anything } - ctx := identity.WithRequester(context.Background(), testUserA) + ctx := claims.WithClaims(context.Background(), testUserA) bucket := memblob.OpenBucket(nil) if false { From 7f1ae1cd54b1ce9f9e6a00e4b9c50187928741ad Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Mon, 12 Aug 2024 09:39:48 +0200 Subject: [PATCH 008/229] Identity: Update authlib to version that has correct commit to claims (#91784) * Update authlib to version that has correct commit to claims --- go.mod | 2 +- go.sum | 4 ++-- pkg/apimachinery/go.mod | 2 +- pkg/apimachinery/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index dc51b413aae..8bf438d9e44 100644 --- a/go.mod +++ b/go.mod @@ -75,7 +75,7 @@ require ( github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20240723124849-f2ab7c7b8f7d // @grafana/alerting-backend - github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06 // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20240812070441-ccb639ea96d0 // @grafana/identity-access-team github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 // @grafana/identity-access-team github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code diff --git a/go.sum b/go.sum index bf3657527dc..4bfd3f335b3 100644 --- a/go.sum +++ b/go.sum @@ -2311,8 +2311,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20240723124849-f2ab7c7b8f7d h1:d2NZeTs+zBPVMd8uOOV5+6lyfs0BCDKxtiNxIMjnPNA= github.com/grafana/alerting v0.0.0-20240723124849-f2ab7c7b8f7d/go.mod h1:DLj8frbtCaITljC2jc0L85JQViPF3mPfOSiYhm1osso= -github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06 h1:qks7nEo/A0+mWvjMjWEIfFD9eIVipb5Lxjfg+HcB5u4= -github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06/go.mod h1:5uu+ADz2c8bVsXheavXS735IcDuO6M3dr+evuDl8rIE= +github.com/grafana/authlib v0.0.0-20240812070441-ccb639ea96d0 h1:LDLHuN0nwa9fwZUKQrOBflePLxzOz4u4AuNutI78AHk= +github.com/grafana/authlib v0.0.0-20240812070441-ccb639ea96d0/go.mod h1:71+xJm0AE6eNGNExUvnABtyEztQ/Acb53/TAdOgwdmc= github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 h1:uD1LcKwvEAqzDsgVChBudPqo5BhPxkj9AgylT5QCReo= github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index a7d155bd03f..b889d39a0b2 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/apimachinery go 1.22.4 require ( - github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06 // @grafana/identity-access-team + github.com/grafana/authlib v0.0.0-20240812070441-ccb639ea96d0 // @grafana/identity-access-team github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 // @grafana/identity-access-team github.com/stretchr/testify v1.9.0 k8s.io/apimachinery v0.31.0-rc.1 diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index e359eb54153..43aee0d9fc3 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -28,8 +28,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN 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= -github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06 h1:qks7nEo/A0+mWvjMjWEIfFD9eIVipb5Lxjfg+HcB5u4= -github.com/grafana/authlib v0.0.0-20240809101159-74eaccc31a06/go.mod h1:5uu+ADz2c8bVsXheavXS735IcDuO6M3dr+evuDl8rIE= +github.com/grafana/authlib v0.0.0-20240812070441-ccb639ea96d0 h1:LDLHuN0nwa9fwZUKQrOBflePLxzOz4u4AuNutI78AHk= +github.com/grafana/authlib v0.0.0-20240812070441-ccb639ea96d0/go.mod h1:71+xJm0AE6eNGNExUvnABtyEztQ/Acb53/TAdOgwdmc= github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 h1:uD1LcKwvEAqzDsgVChBudPqo5BhPxkj9AgylT5QCReo= github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= From ab3e8652aa865e43a3f2c164b626c42dfffab33e Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Mon, 12 Aug 2024 09:56:42 +0100 Subject: [PATCH 009/229] Adhoc Filters: add new feature toggle for 'one of' operator (#91688) add new feature toggle for 'one of' --- .../configure-grafana/feature-toggles/index.md | 1 + .../grafana-data/src/types/featureToggles.gen.ts | 1 + pkg/services/featuremgmt/registry.go | 6 ++++++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 ++++ pkg/services/featuremgmt/toggles_gen.json | 12 ++++++++++++ 6 files changed, 25 insertions(+) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 2d0289f9e20..ea5b8257c97 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -192,6 +192,7 @@ Experimental features might be changed or removed without prior notice. | `alertingApiServer` | Register Alerting APIs with the K8s API server | | `dashboardRestoreUI` | Enables the frontend to be able to restore a recently deleted dashboard | | `dataplaneAggregator` | Enable grafana dataplane aggregator | +| `adhocFilterOneOf` | Exposes a new 'one of' operator for ad-hoc filters. This operator allows users to filter by multiple values in a single filter. | ## Development feature toggles diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 80f49756040..b8bd9eff3d6 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -200,4 +200,5 @@ export interface FeatureToggles { cloudwatchMetricInsightsCrossAccount?: boolean; prometheusAzureOverrideAudience?: boolean; dataplaneAggregator?: boolean; + adhocFilterOneOf?: boolean; } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index ad9f3c66109..d64ffd3159d 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1379,6 +1379,12 @@ var ( Owner: grafanaAppPlatformSquad, RequiresRestart: true, }, + { + Name: "adhocFilterOneOf", + Description: "Exposes a new 'one of' operator for ad-hoc filters. This operator allows users to filter by multiple values in a single filter.", + Stage: FeatureStageExperimental, + Owner: grafanaDashboardsSquad, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index cfc84346101..a5d23847693 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -181,3 +181,4 @@ bodyScrolling,preview,@grafana/grafana-frontend-platform,false,false,true cloudwatchMetricInsightsCrossAccount,preview,@grafana/aws-datasources,false,false,true prometheusAzureOverrideAudience,deprecated,@grafana/partner-datasources,false,false,false dataplaneAggregator,experimental,@grafana/grafana-app-platform-squad,false,true,false +adhocFilterOneOf,experimental,@grafana/dashboards-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 4d61160b851..02379fbfc40 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -734,4 +734,8 @@ const ( // FlagDataplaneAggregator // Enable grafana dataplane aggregator FlagDataplaneAggregator = "dataplaneAggregator" + + // FlagAdhocFilterOneOf + // Exposes a new 'one of' operator for ad-hoc filters. This operator allows users to filter by multiple values in a single filter. + FlagAdhocFilterOneOf = "adhocFilterOneOf" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 2ab830e5681..4421f43be0b 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -45,6 +45,18 @@ "expression": "true" } }, + { + "metadata": { + "name": "adhocFilterOneOf", + "resourceVersion": "1723119716623", + "creationTimestamp": "2024-08-08T12:21:56Z" + }, + "spec": { + "description": "Exposes a new 'one of' operator for ad-hoc filters. This operator allows users to filter by multiple values in a single filter.", + "stage": "experimental", + "codeowner": "@grafana/dashboards-squad" + } + }, { "metadata": { "name": "aiGeneratedDashboardChanges", From 5bae9f11bca857c4f78e8334607944cacdc22c88 Mon Sep 17 00:00:00 2001 From: Ieva Date: Mon, 12 Aug 2024 09:58:55 +0100 Subject: [PATCH 010/229] Docs: Add docs for bulk updating team members (#91499) add docs for bulk updating team members --- docs/sources/developers/http_api/team.md | 46 ++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/sources/developers/http_api/team.md b/docs/sources/developers/http_api/team.md index 67e12facf86..c3bfab47411 100644 --- a/docs/sources/developers/http_api/team.md +++ b/docs/sources/developers/http_api/team.md @@ -404,6 +404,52 @@ Status Codes: - **403** - Permission denied - **404** - Team not found/Team member not found +## Bulk Update Team Members + +Allows bulk updating team members and administrators using user emails. +Will override all current members and administrators for the specified team. + +`PUT /api/teams/:teamId/members + +**Required permissions** + +See note in the [introduction]({{< ref "#team-api" >}}) for an explanation. + +| Action | Scope | +| ----------------------- | -------- | +| teams.permissions:write | teams:\* | + +**Example Request**: + +```http +PUT /api/teams/1/members HTTP/1.1 +Accept: application/json +Content-Type: application/json +Authorization: Bearer glsa_kcVxDhZtu5ISOZIEt + +{ + "members": ["user1@email.com", "user2@email.com"] + "admins": ["user3@email.com"] +} +``` + +**Example Response**: + +```http +HTTP/1.1 200 +Content-Type: application/json + +{"message":"Team memberships have been updated"} +``` + +Status Codes: + +- **200** - Ok +- **401** - Unauthorized +- **403** - Permission denied +- **404** - Team not found/Team member not found +- **500** - Internal error + ## Get Team Preferences `GET /api/teams/:teamId/preferences` From 6e7bc028d0b9952bcf0a2434e6efc1aadb0da218 Mon Sep 17 00:00:00 2001 From: Ieva Date: Mon, 12 Aug 2024 10:07:33 +0100 Subject: [PATCH 011/229] RBCA: Better separation between action set svc and store (#91491) better separation between action set svc and store --- .../resourcepermissions/service.go | 136 ++++++++++++-- .../resourcepermissions/service_test.go | 172 +++++++++++++++++ .../resourcepermissions/store.go | 92 ++------- .../resourcepermissions/store_test.go | 176 +----------------- 4 files changed, 315 insertions(+), 261 deletions(-) diff --git a/pkg/services/accesscontrol/resourcepermissions/service.go b/pkg/services/accesscontrol/resourcepermissions/service.go index 7808f0a7665..82fe2c6e99e 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service.go +++ b/pkg/services/accesscontrol/resourcepermissions/service.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "sort" + "strings" "golang.org/x/exp/slices" @@ -12,7 +13,9 @@ import ( "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/licensing" @@ -23,7 +26,7 @@ import ( "github.com/grafana/grafana/pkg/setting" ) -var _ pluginaccesscontrol.ActionSetRegistry = (*InMemoryActionSets)(nil) +var _ pluginaccesscontrol.ActionSetRegistry = (ActionSetService)(nil) type Store interface { // SetUserResourcePermission sets permission for managed user role on a resource @@ -437,7 +440,7 @@ type ActionSetService interface { ResolveAction(action string) []string // ResolveActionSet resolves an action set to a list of corresponding actions. ResolveActionSet(actionSet string) []string - + // StoreActionSet stores action set. If a set with the given name has already been stored, the new actions will be appended to the existing actions. StoreActionSet(name string, actions []string) pluginaccesscontrol.ActionSetRegistry @@ -450,21 +453,126 @@ type ActionSet struct { Actions []string `json:"actions"` } -// InMemoryActionSets is an in-memory implementation of the ActionSetService. -type InMemoryActionSets struct { - features featuremgmt.FeatureToggles - log log.Logger - actionSetToActions map[string][]string - actionToActionSets map[string][]string +type ActionSetStore interface { + // StoreActionSet stores action set. If a set with the given name has already been stored, the new actions will be appended to the existing actions. + StoreActionSet(name string, actions []string) + // ResolveActionSet resolves an action set to a list of corresponding actions. + ResolveActionSet(actionSet string) []string + // ResolveAction returns all the action sets that the action belongs to. + ResolveAction(action string) []string + // ResolveActionPrefix returns all action sets that include at least one action with the specified prefix + ResolveActionPrefix(prefix string) []string + // ExpandActionSetsWithFilter takes a set of permissions that might include some action set permissions, and returns a set of permissions with action sets expanded into underlying permissions. + // When action sets are expanded into the underlying permissions only those permissions whose action is matched by actionMatcher are included. + ExpandActionSetsWithFilter(permissions []accesscontrol.Permission, actionMatcher func(action string) bool) []accesscontrol.Permission +} + +type ActionSetSvc struct { + features featuremgmt.FeatureToggles + store ActionSetStore } // NewActionSetService returns a new instance of InMemoryActionSetService. func NewActionSetService(features featuremgmt.FeatureToggles) ActionSetService { - actionSets := &InMemoryActionSets{ - features: features, - log: log.New("resourcepermissions.actionsets"), - actionSetToActions: make(map[string][]string), - actionToActionSets: make(map[string][]string), + return &ActionSetSvc{ + features: features, + store: NewInMemoryActionSetStore(features), + } +} + +// ResolveAction returns all the action sets that the action belongs to. +func (a *ActionSetSvc) ResolveAction(action string) []string { + sets := a.store.ResolveAction(action) + filteredSets := make([]string, 0, len(sets)) + for _, set := range sets { + // Only use action sets for folders and dashboards for now + // We need to verify that action sets for other resources do not share names with actions (eg, `datasources:read`) + if !isFolderOrDashboardAction(set) { + continue + } + filteredSets = append(filteredSets, set) + } + + return filteredSets +} + +// ResolveActionPrefix returns all action sets that include at least one action with the specified prefix +func (a *ActionSetSvc) ResolveActionPrefix(actionPrefix string) []string { + sets := a.store.ResolveActionPrefix(actionPrefix) + filteredSets := make([]string, 0, len(sets)) + for _, set := range sets { + // Only use action sets for folders and dashboards for now + // We need to verify that action sets for other resources do not share names with actions (eg, `datasources:read`) + if !isFolderOrDashboardAction(set) { + continue + } + filteredSets = append(filteredSets, set) + } + + return filteredSets +} + +// ResolveActionSet resolves an action set to a list of corresponding actions. +func (a *ActionSetSvc) ResolveActionSet(actionSet string) []string { + // Only use action sets for folders and dashboards for now + // We need to verify that action sets for other resources do not share names with actions (eg, `datasources:read`) + if !isFolderOrDashboardAction(actionSet) { + return nil } - return actionSets + return a.store.ResolveActionSet(actionSet) +} + +// StoreActionSet stores action set. If a set with the given name has already been stored, the new actions will be appended to the existing actions. +func (a *ActionSetSvc) StoreActionSet(name string, actions []string) { + // To avoid backwards incompatible changes, we don't want to store these actions in the DB + // Once action sets are fully enabled, we can include dashboards.ActionFoldersCreate in the list of other folder edit/admin actions + // Tracked in https://github.com/grafana/identity-access-team/issues/794 + if name == "folders:edit" || name == "folders:admin" { + if !slices.Contains(a.ResolveActionSet(name), dashboards.ActionFoldersCreate) { + actions = append(actions, dashboards.ActionFoldersCreate) + } + } + + a.store.StoreActionSet(name, actions) +} + +// ExpandActionSets takes a set of permissions that might include some action set permissions, and returns a set of permissions with action sets expanded into underlying permissions +func (a *ActionSetSvc) ExpandActionSets(permissions []accesscontrol.Permission) []accesscontrol.Permission { + actionMatcher := func(_ string) bool { + return true + } + return a.ExpandActionSetsWithFilter(permissions, actionMatcher) +} + +// ExpandActionSetsWithFilter works like ExpandActionSets, but it also takes a function for action filtering. When action sets are expanded into the underlying permissions, +// only those permissions whose action is matched by actionMatcher are included. +func (a *ActionSetSvc) ExpandActionSetsWithFilter(permissions []accesscontrol.Permission, actionMatcher func(action string) bool) []accesscontrol.Permission { + return a.store.ExpandActionSetsWithFilter(permissions, actionMatcher) +} + +// RegisterActionSets allow the caller to expand the existing action sets with additional permissions +// This is intended to be used by plugins, and currently supports extending folder and dashboard action sets +func (a *ActionSetSvc) RegisterActionSets(ctx context.Context, pluginID string, registrations []plugins.ActionSet) error { + if !a.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) || !a.features.IsEnabled(ctx, featuremgmt.FlagAccessControlOnCall) { + return nil + } + for _, reg := range registrations { + if err := pluginutils.ValidatePluginActionSet(pluginID, reg); err != nil { + return err + } + a.StoreActionSet(reg.Action, reg.Actions) + } + return nil +} + +func isFolderOrDashboardAction(action string) bool { + return strings.HasPrefix(action, dashboards.ScopeDashboardsRoot) || strings.HasPrefix(action, dashboards.ScopeFoldersRoot) +} + +// GetActionSetName function creates an action set from a list of actions and stores it inmemory. +func GetActionSetName(resource, permission string) string { + // lower cased + resource = strings.ToLower(resource) + permission = strings.ToLower(permission) + return fmt.Sprintf("%s:%s", resource, permission) } diff --git a/pkg/services/accesscontrol/resourcepermissions/service_test.go b/pkg/services/accesscontrol/resourcepermissions/service_test.go index 1da32d62d3e..94ead810dd7 100644 --- a/pkg/services/accesscontrol/resourcepermissions/service_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/service_test.go @@ -10,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" @@ -314,6 +315,177 @@ func TestService_RegisterActionSets(t *testing.T) { } } +func TestStore_RegisterActionSet(t *testing.T) { + type actionSetTest struct { + desc string + features featuremgmt.FeatureToggles + pluginID string + pluginActions []plugins.ActionSet + coreActionSets []ActionSet + expectedErr bool + expectedActionSets []ActionSet + } + + tests := []actionSetTest{ + { + desc: "should be able to register a plugin action set if the right feature toggles are enabled", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + }, + expectedActionSets: []ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + }, + }, + { + desc: "should not register plugin action set if feature toggles are missing", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + }, + expectedActionSets: []ActionSet{}, + }, + { + desc: "should be able to register multiple plugin action sets", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, + }, + }, + expectedActionSets: []ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, + }, + }, + }, + { + desc: "action set actions should be added not replaced", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, + }, + }, + coreActionSets: []ActionSet{ + { + Action: "folders:view", + Actions: []string{"folders:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"folders:write", "folders:delete"}, + }, + { + Action: "folders:admin", + Actions: []string{"folders.permissions:read"}, + }, + }, + expectedActionSets: []ActionSet{ + { + Action: "folders:view", + Actions: []string{"folders:read", "test-app.resource:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"folders:write", "test-app.resource:write", "folders:delete", "test-app.resource:delete"}, + }, + { + Action: "folders:admin", + Actions: []string{"folders.permissions:read"}, + }, + }, + }, + { + desc: "should not be able to register an action that doesn't have a plugin prefix", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:view", + Actions: []string{"test-app.resource:read"}, + }, + { + Action: "folders:edit", + Actions: []string{"users:read", "test-app.resource:delete"}, + }, + }, + expectedErr: true, + }, + { + desc: "should not be able to register action set that is not in the allow list", + features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), + pluginID: "test-app", + pluginActions: []plugins.ActionSet{ + { + Action: "folders:super-admin", + Actions: []string{"test-app.resource:read"}, + }, + }, + expectedErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + asService := NewActionSetService(tt.features) + + err := asService.RegisterActionSets(context.Background(), tt.pluginID, tt.pluginActions) + if tt.expectedErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + for _, set := range tt.coreActionSets { + asService.StoreActionSet(set.Action, set.Actions) + } + + for _, expected := range tt.expectedActionSets { + actions := asService.ResolveActionSet(expected.Action) + if expected.Action == "folders:edit" || expected.Action == "folders:admin" { + expected.Actions = append(expected.Actions, "folders:create") + } + assert.ElementsMatch(t, expected.Actions, actions) + } + + if len(tt.expectedActionSets) == 0 { + for _, set := range tt.pluginActions { + registeredActions := asService.ResolveActionSet(set.Action) + assert.Empty(t, registeredActions, "no actions from plugin action sets should have been registered") + } + } + }) + } +} + func setupTestEnvironment(t *testing.T, ops Options) (*Service, user.Service, team.Service) { t.Helper() diff --git a/pkg/services/accesscontrol/resourcepermissions/store.go b/pkg/services/accesscontrol/resourcepermissions/store.go index 5e4b45b7442..755e1977ff9 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store.go +++ b/pkg/services/accesscontrol/resourcepermissions/store.go @@ -3,15 +3,12 @@ package resourcepermissions import ( "context" "fmt" - "slices" "strings" "time" "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/plugins" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/accesscontrol" - "github.com/grafana/grafana/pkg/services/accesscontrol/pluginutils" - "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/serviceaccounts" @@ -744,6 +741,23 @@ func managedPermission(action, resource string, resourceID, resourceAttribute st } } +// InMemoryActionSets is an in-memory implementation of the ActionSetStore. +type InMemoryActionSets struct { + features featuremgmt.FeatureToggles + log log.Logger + actionSetToActions map[string][]string + actionToActionSets map[string][]string +} + +func NewInMemoryActionSetStore(features featuremgmt.FeatureToggles) *InMemoryActionSets { + return &InMemoryActionSets{ + actionSetToActions: make(map[string][]string), + actionToActionSets: make(map[string][]string), + log: log.New("resourcepermissions.actionsets"), + features: features, + } +} + // ResolveActionPrefix returns all action sets that include at least one action with the specified prefix func (s *InMemoryActionSets) ResolveActionPrefix(prefix string) []string { if prefix == "" { @@ -753,11 +767,6 @@ func (s *InMemoryActionSets) ResolveActionPrefix(prefix string) []string { sets := make([]string, 0, len(s.actionSetToActions)) for set, actions := range s.actionSetToActions { - // Only use action sets for folders and dashboards for now - // We need to verify that action sets for other resources do not share names with actions (eg, `datasources:read`) - if !isFolderOrDashboardAction(set) { - continue - } for _, action := range actions { if strings.HasPrefix(action, prefix) { sets = append(sets, set) @@ -770,44 +779,13 @@ func (s *InMemoryActionSets) ResolveActionPrefix(prefix string) []string { } func (s *InMemoryActionSets) ResolveAction(action string) []string { - actionSets := s.actionToActionSets[action] - sets := make([]string, 0, len(actionSets)) - - for _, actionSet := range actionSets { - // Only use action sets for folders and dashboards for now - // We need to verify that action sets for other resources do not share names with actions (eg, `datasources:read`) - if !isFolderOrDashboardAction(actionSet) { - continue - } - sets = append(sets, actionSet) - } - - return sets + return s.actionToActionSets[action] } func (s *InMemoryActionSets) ResolveActionSet(actionSet string) []string { - // Only use action sets for folders and dashboards for now - // We need to verify that action sets for other resources do not share names with actions (eg, `datasources:read`) - if !isFolderOrDashboardAction(actionSet) { - return nil - } return s.actionSetToActions[actionSet] } -func isFolderOrDashboardAction(action string) bool { - return strings.HasPrefix(action, dashboards.ScopeDashboardsRoot) || strings.HasPrefix(action, dashboards.ScopeFoldersRoot) -} - -// ExpandActionSets takes a set of permissions that might include some action set permissions, and returns a set of permissions with action sets expanded into underlying permissions -func (s *InMemoryActionSets) ExpandActionSets(permissions []accesscontrol.Permission) []accesscontrol.Permission { - actionMatcher := func(_ string) bool { - return true - } - return s.ExpandActionSetsWithFilter(permissions, actionMatcher) -} - -// ExpandActionSetsWithFilter works like ExpandActionSets, but it also takes a function for action filtering. When action sets are expanded into the underlying permissions, -// only those permissions whose action is matched by actionMatcher are included. func (s *InMemoryActionSets) ExpandActionSetsWithFilter(permissions []accesscontrol.Permission, actionMatcher func(action string) bool) []accesscontrol.Permission { var expandedPermissions []accesscontrol.Permission for _, permission := range permissions { @@ -828,15 +806,6 @@ func (s *InMemoryActionSets) ExpandActionSetsWithFilter(permissions []accesscont } func (s *InMemoryActionSets) StoreActionSet(name string, actions []string) { - // To avoid backwards incompatible changes, we don't want to store these actions in the DB - // Once action sets are fully enabled, we can include dashboards.ActionFoldersCreate in the list of other folder edit/admin actions - // Tracked in https://github.com/grafana/identity-access-team/issues/794 - if name == "folders:edit" || name == "folders:admin" { - if !slices.Contains(s.actionSetToActions[name], dashboards.ActionFoldersCreate) { - actions = append(actions, dashboards.ActionFoldersCreate) - } - } - s.actionSetToActions[name] = append(s.actionSetToActions[name], actions...) for _, action := range actions { @@ -847,26 +816,3 @@ func (s *InMemoryActionSets) StoreActionSet(name string, actions []string) { } s.log.Debug("stored action set", "action set name", name) } - -// RegisterActionSets allow the caller to expand the existing action sets with additional permissions -// This is intended to be used by plugins, and currently supports extending folder and dashboard action sets -func (s *InMemoryActionSets) RegisterActionSets(ctx context.Context, pluginID string, registrations []plugins.ActionSet) error { - if !s.features.IsEnabled(ctx, featuremgmt.FlagAccessActionSets) || !s.features.IsEnabled(ctx, featuremgmt.FlagAccessControlOnCall) { - return nil - } - for _, reg := range registrations { - if err := pluginutils.ValidatePluginActionSet(pluginID, reg); err != nil { - return err - } - s.StoreActionSet(reg.Action, reg.Actions) - } - return nil -} - -// GetActionSetName function creates an action set from a list of actions and stores it inmemory. -func GetActionSetName(resource, permission string) string { - // lower cased - resource = strings.ToLower(resource) - permission = strings.ToLower(permission) - return fmt.Sprintf("%s:%s", resource, permission) -} diff --git a/pkg/services/accesscontrol/resourcepermissions/store_test.go b/pkg/services/accesscontrol/resourcepermissions/store_test.go index 1c8cc91107d..de5b5de7c82 100644 --- a/pkg/services/accesscontrol/resourcepermissions/store_test.go +++ b/pkg/services/accesscontrol/resourcepermissions/store_test.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -782,183 +781,12 @@ func TestStore_StoreActionSet(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { - asService := NewActionSetService(featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets)) + asService := NewInMemoryActionSetStore(featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets)) asService.StoreActionSet(GetActionSetName(tt.resource, tt.action), tt.actions) actionSetName := GetActionSetName(tt.resource, tt.action) actionSet := asService.ResolveActionSet(actionSetName) - require.Equal(t, append(tt.actions, "folders:create"), actionSet) - }) - } -} - -func TestStore_RegisterActionSet(t *testing.T) { - type actionSetTest struct { - desc string - features featuremgmt.FeatureToggles - pluginID string - pluginActions []plugins.ActionSet - coreActionSets []ActionSet - expectedErr bool - expectedActionSets []ActionSet - } - - tests := []actionSetTest{ - { - desc: "should be able to register a plugin action set if the right feature toggles are enabled", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - }, - expectedActionSets: []ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - }, - }, - { - desc: "should not register plugin action set if feature toggles are missing", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - }, - expectedActionSets: []ActionSet{}, - }, - { - desc: "should be able to register multiple plugin action sets", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, - }, - }, - expectedActionSets: []ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, - }, - }, - }, - { - desc: "action set actions should be added not replaced", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"test-app.resource:write", "test-app.resource:delete"}, - }, - }, - coreActionSets: []ActionSet{ - { - Action: "folders:view", - Actions: []string{"folders:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"folders:write", "folders:delete"}, - }, - { - Action: "folders:admin", - Actions: []string{"folders.permissions:read"}, - }, - }, - expectedActionSets: []ActionSet{ - { - Action: "folders:view", - Actions: []string{"folders:read", "test-app.resource:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"folders:write", "test-app.resource:write", "folders:delete", "test-app.resource:delete"}, - }, - { - Action: "folders:admin", - Actions: []string{"folders.permissions:read"}, - }, - }, - }, - { - desc: "should not be able to register an action that doesn't have a plugin prefix", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:view", - Actions: []string{"test-app.resource:read"}, - }, - { - Action: "folders:edit", - Actions: []string{"users:read", "test-app.resource:delete"}, - }, - }, - expectedErr: true, - }, - { - desc: "should not be able to register action set that is not in the allow list", - features: featuremgmt.WithFeatures(featuremgmt.FlagAccessActionSets, featuremgmt.FlagAccessControlOnCall), - pluginID: "test-app", - pluginActions: []plugins.ActionSet{ - { - Action: "folders:super-admin", - Actions: []string{"test-app.resource:read"}, - }, - }, - expectedErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.desc, func(t *testing.T) { - asService := NewActionSetService(tt.features) - - err := asService.RegisterActionSets(context.Background(), tt.pluginID, tt.pluginActions) - if tt.expectedErr { - require.Error(t, err) - return - } - require.NoError(t, err) - - for _, set := range tt.coreActionSets { - asService.StoreActionSet(set.Action, set.Actions) - } - - for _, expected := range tt.expectedActionSets { - actions := asService.ResolveActionSet(expected.Action) - if expected.Action == "folders:edit" || expected.Action == "folders:admin" { - expected.Actions = append(expected.Actions, "folders:create") - } - assert.ElementsMatch(t, expected.Actions, actions) - } - - if len(tt.expectedActionSets) == 0 { - for _, set := range tt.pluginActions { - registeredActions := asService.ResolveActionSet(set.Action) - assert.Empty(t, registeredActions, "no actions from plugin action sets should have been registered") - } - } + require.Equal(t, tt.actions, actionSet) }) } } From 82d051f336f3cd0c06f7562897659c8e096d02e2 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Mon, 12 Aug 2024 17:11:45 +0700 Subject: [PATCH 012/229] AzureMonitor: Mark Azure Prometheus exemplars as public preview (#91674) Mark Azure Prom exemplars as public preview --- .../configure-grafana/feature-toggles/index.md | 2 +- pkg/services/featuremgmt/registry.go | 2 +- pkg/services/featuremgmt/toggles_gen.csv | 2 +- pkg/services/featuremgmt/toggles_gen.json | 9 ++++++--- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index ea5b8257c97..70a4dcfdae9 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -101,6 +101,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `onPremToCloudMigrations` | Enable the Grafana Migration Assistant, which helps you easily migrate on-prem dashboards, folders, and data source configurations to your Grafana Cloud stack. | | `newPDFRendering` | New implementation for the dashboard-to-PDF rendering | | `ssoSettingsSAML` | Use the new SSO Settings API to configure the SAML connector | +| `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars | | `openSearchBackendFlowEnabled` | Enables the backend query flow for Open Search datasource plugin | | `cloudwatchMetricInsightsCrossAccount` | Enables cross account observability for Cloudwatch Metric Insights query builder | @@ -185,7 +186,6 @@ Experimental features might be changed or removed without prior notice. | `notificationBanner` | Enables the notification banner UI and API | | `dashboardRestore` | Enables deleted dashboard restore feature (backend only) | | `alertingCentralAlertHistory` | Enables the new central alert history. | -| `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars | | `pinNavItems` | Enables pinning of nav items | | `failWrongDSUID` | Throws an error if a datasource has an invalid UIDs | | `databaseReadReplica` | Use a read replica for some database queries. | diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index d64ffd3159d..9964b58d5b6 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1263,7 +1263,7 @@ var ( { Name: "azureMonitorPrometheusExemplars", Description: "Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars", - Stage: FeatureStageExperimental, + Stage: FeatureStagePublicPreview, Owner: grafanaPartnerPluginsSquad, }, { diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index a5d23847693..5206e0500f8 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -165,7 +165,7 @@ alertingDisableSendAlertsExternal,experimental,@grafana/alerting-squad,false,fal preserveDashboardStateWhenNavigating,experimental,@grafana/dashboards-squad,false,false,false alertingCentralAlertHistory,experimental,@grafana/alerting-squad,false,false,true pluginProxyPreserveTrailingSlash,GA,@grafana/plugins-platform-backend,false,false,false -azureMonitorPrometheusExemplars,experimental,@grafana/partner-datasources,false,false,false +azureMonitorPrometheusExemplars,preview,@grafana/partner-datasources,false,false,false pinNavItems,experimental,@grafana/grafana-frontend-platform,false,false,false authZGRPCServer,experimental,@grafana/identity-access-team,false,false,false openSearchBackendFlowEnabled,preview,@grafana/aws-datasources,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 4421f43be0b..0167e963b1e 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -517,12 +517,15 @@ { "metadata": { "name": "azureMonitorPrometheusExemplars", - "resourceVersion": "1718727528075", - "creationTimestamp": "2024-06-06T16:53:17Z" + "resourceVersion": "1723028568258", + "creationTimestamp": "2024-06-06T16:53:17Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-08-07 11:02:48.258776 +0000 UTC" + } }, "spec": { "description": "Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars", - "stage": "experimental", + "stage": "preview", "codeowner": "@grafana/partner-datasources" } }, From 15f2b08f00f8293d8b524740bf51e1b24d0be3c4 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Mon, 12 Aug 2024 12:13:26 +0200 Subject: [PATCH 013/229] Alerting: Catch alert rule search exceptions (#91788) catch search exceptions and prevent from bubbling --- .../unified/hooks/useFilteredRules.test.ts | 22 ++++++++++++++++--- .../unified/hooks/useFilteredRules.ts | 18 ++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/public/app/features/alerting/unified/hooks/useFilteredRules.test.ts b/public/app/features/alerting/unified/hooks/useFilteredRules.test.ts index db277cea39d..f6bbe0164f8 100644 --- a/public/app/features/alerting/unified/hooks/useFilteredRules.test.ts +++ b/public/app/features/alerting/unified/hooks/useFilteredRules.test.ts @@ -273,9 +273,25 @@ describe('filterRules', function () { const ruleQuery = '[alongnameinthefirstgroup][thishas spaces][somethingelse]'; const namespaceQuery = 'foo|bar'; const groupQuery = 'some|group'; + const freeForm = '.+'; + + expect(() => + filterRules( + [ns], + getFilter({ groupName: groupQuery, ruleName: ruleQuery, namespace: namespaceQuery, freeFormWords: [freeForm] }) + ) + ).not.toThrow(); + }); + + // these test may same to be the same as the one above but it tests different edge-cases + it('does not crash with other regex values', () => { + const rules = [mockCombinedRule({ name: 'rule' })]; + + const ns = mockCombinedRuleNamespace({ + name: 'namespace', + groups: [mockCombinedRuleGroup('group', rules)], + }); - const performFilter = () => - filterRules([ns], getFilter({ groupName: groupQuery, ruleName: ruleQuery, namespace: namespaceQuery })); - expect(performFilter).not.toThrow(); + expect(() => filterRules([ns], getFilter({ freeFormWords: ['.+'] }))).not.toThrow(); }); }); diff --git a/public/app/features/alerting/unified/hooks/useFilteredRules.ts b/public/app/features/alerting/unified/hooks/useFilteredRules.ts index f7674c92500..d515a79ab89 100644 --- a/public/app/features/alerting/unified/hooks/useFilteredRules.ts +++ b/public/app/features/alerting/unified/hooks/useFilteredRules.ts @@ -8,6 +8,7 @@ import { Matcher } from 'app/plugins/datasource/alertmanager/types'; import { CombinedRuleGroup, CombinedRuleNamespace, Rule } from 'app/types/unified-alerting'; import { isPromAlertingRuleState, PromRuleType, RulerGrafanaRuleDTO } from 'app/types/unified-alerting-dto'; +import { logError } from '../Analytics'; import { applySearchFilterToQuery, getSearchFilterFromQuery, RulesFilter } from '../search/rulesSearchParser'; import { labelsMatchMatchers, matcherToMatcherField } from '../utils/alertmanager'; import { Annotation } from '../utils/constants'; @@ -28,6 +29,8 @@ import { useURLSearchParams } from './useURLSearchParams'; const MAX_NEEDLE_SIZE = 25; const INFO_THRESHOLD = Infinity; +const SEARCH_FAILED_ERR = new Error('Failed to search rules'); + /** * Escape query strings so that regex characters don't interfere * with uFuzzy search methods. @@ -160,7 +163,20 @@ export const filterRules = ( } // If a namespace and group have rules that match the rules filters then keep them. - return filteredNamespaces.reduce(reduceNamespaces(filterState), []); + const filteredRuleNamespaces: CombinedRuleNamespace[] = []; + + try { + const matches = filteredNamespaces.reduce(reduceNamespaces(filterState), []); + matches.forEach((match) => { + filteredRuleNamespaces.push(match); + }); + } catch { + logError(SEARCH_FAILED_ERR, { + search: JSON.stringify(filterState), + }); + } + + return filteredRuleNamespaces; }; const reduceNamespaces = (filterState: RulesFilter) => { From 3303900c09743a29dc40740b1a1105e4914679fe Mon Sep 17 00:00:00 2001 From: Bogdan Matei Date: Mon, 12 Aug 2024 14:11:50 +0300 Subject: [PATCH 014/229] Scopes: Lift scopes at global level (#90136) --- .betterer.results | 7 +- .github/CODEOWNERS | 1 + public/app/app.ts | 3 + .../core/components/AppChrome/AppChrome.tsx | 2 + .../AppChrome/TopBar/TopSearchBar.tsx | 2 + .../AppChrome/TopBar/TopSearchBarSection.tsx | 5 +- .../embedding/EmbeddedDashboard.tsx | 19 +- .../pages/DashboardScenePageStateManager.ts | 10 +- .../panel-edit/PanelEditorRenderer.tsx | 23 +- .../dashboard-scene/scene/DashboardScene.tsx | 25 +- .../scene/DashboardSceneRenderer.tsx | 32 +-- .../scene/Scopes/ScopesDashboardsScene.tsx | 164 ----------- .../scene/Scopes/ScopesScene.tsx | 148 ---------- .../dashboard-scene/scene/Scopes/utils.ts | 28 -- .../transformSaveModelToScene.ts | 5 + .../features/dashboard/api/dashboard_api.ts | 6 +- .../dashboard/utils/getScopesFromUrl.ts | 13 - .../app/features/scopes/ScopesDashboards.tsx | 9 + .../app/features/scopes/ScopesFacadeScene.ts | 53 ++++ public/app/features/scopes/ScopesSelector.tsx | 9 + public/app/features/scopes/index.ts | 14 + public/app/features/scopes/instance.tsx | 21 ++ .../scopes/internal/ScopesDashboardsScene.tsx | 262 ++++++++++++++++++ .../internal}/ScopesInput.tsx | 36 +-- .../internal/ScopesSelectorScene.tsx} | 192 ++++++++----- .../Scopes => scopes/internal}/ScopesTree.tsx | 0 .../internal}/ScopesTreeHeadline.tsx | 0 .../internal}/ScopesTreeItem.tsx | 0 .../internal}/ScopesTreeLoading.tsx | 0 .../internal}/ScopesTreeSearch.tsx | 0 .../scene/Scopes => scopes/internal}/api.ts | 8 +- public/app/features/scopes/internal/const.ts | 1 + .../scene/Scopes => scopes/internal}/types.ts | 0 public/app/features/scopes/internal/utils.ts | 45 +++ .../scopes.test.tsx} | 240 ++++++++-------- .../scene/Scopes => scopes}/testUtils.tsx | 71 +++-- public/app/features/scopes/utils.ts | 39 +++ public/locales/en-US/grafana.json | 25 +- public/locales/pseudo-LOCALE/grafana.json | 25 +- public/test/test-utils.tsx | 2 +- 40 files changed, 862 insertions(+), 683 deletions(-) delete mode 100644 public/app/features/dashboard-scene/scene/Scopes/ScopesDashboardsScene.tsx delete mode 100644 public/app/features/dashboard-scene/scene/Scopes/ScopesScene.tsx delete mode 100644 public/app/features/dashboard-scene/scene/Scopes/utils.ts delete mode 100644 public/app/features/dashboard/utils/getScopesFromUrl.ts create mode 100644 public/app/features/scopes/ScopesDashboards.tsx create mode 100644 public/app/features/scopes/ScopesFacadeScene.ts create mode 100644 public/app/features/scopes/ScopesSelector.tsx create mode 100644 public/app/features/scopes/index.ts create mode 100644 public/app/features/scopes/instance.tsx create mode 100644 public/app/features/scopes/internal/ScopesDashboardsScene.tsx rename public/app/features/{dashboard-scene/scene/Scopes => scopes/internal}/ScopesInput.tsx (80%) rename public/app/features/{dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx => scopes/internal/ScopesSelectorScene.tsx} (63%) rename public/app/features/{dashboard-scene/scene/Scopes => scopes/internal}/ScopesTree.tsx (100%) rename public/app/features/{dashboard-scene/scene/Scopes => scopes/internal}/ScopesTreeHeadline.tsx (100%) rename public/app/features/{dashboard-scene/scene/Scopes => scopes/internal}/ScopesTreeItem.tsx (100%) rename public/app/features/{dashboard-scene/scene/Scopes => scopes/internal}/ScopesTreeLoading.tsx (100%) rename public/app/features/{dashboard-scene/scene/Scopes => scopes/internal}/ScopesTreeSearch.tsx (100%) rename public/app/features/{dashboard-scene/scene/Scopes => scopes/internal}/api.ts (91%) create mode 100644 public/app/features/scopes/internal/const.ts rename public/app/features/{dashboard-scene/scene/Scopes => scopes/internal}/types.ts (100%) create mode 100644 public/app/features/scopes/internal/utils.ts rename public/app/features/{dashboard-scene/scene/Scopes/ScopesScene.test.tsx => scopes/scopes.test.tsx} (77%) rename public/app/features/{dashboard-scene/scene/Scopes => scopes}/testUtils.tsx (88%) create mode 100644 public/app/features/scopes/utils.ts diff --git a/.betterer.results b/.betterer.results index 73851418191..c0ff4acd23b 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2864,9 +2864,6 @@ exports[`better eslint`] = { "public/app/features/dashboard-scene/scene/RowRepeaterBehavior.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] ], - "public/app/features/dashboard-scene/scene/Scopes/ScopesInput.tsx:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/features/dashboard-scene/scene/row-actions/RowActions.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], @@ -5184,6 +5181,10 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] ], + "public/app/features/scopes/index.ts:5381": [ + [0, 0, 0, "Do not re-export imported variable (\`./instance\`)", "0"], + [0, 0, 0, "Do not re-export imported variable (\`./ScopesDashboards\`)", "1"] + ], "public/app/features/search/page/components/ActionRow.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2b6664d8dbf..a14dacb4487 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -417,6 +417,7 @@ playwright.config.ts @grafana/plugins-platform-frontend /public/app/features/dashboard/ @grafana/dashboards-squad /public/app/features/dashboard/components/TransformationsEditor/ @grafana/dataviz-squad /public/app/features/dashboard-scene/ @grafana/dashboards-squad +/public/app/features/scopes/ @grafana/dashboards-squad /public/app/features/datasources/ @grafana/plugins-platform-frontend /public/app/features/dimensions/ @grafana/dataviz-squad /public/app/features/dataframe-import/ @grafana/dataviz-squad diff --git a/public/app/app.ts b/public/app/app.ts index a6d2f7512a8..467f8bcca92 100644 --- a/public/app/app.ts +++ b/public/app/app.ts @@ -92,6 +92,7 @@ import { preloadPlugins } from './features/plugins/pluginPreloader'; import { QueryRunner } from './features/query/state/QueryRunner'; import { runRequest } from './features/query/state/runRequest'; import { initWindowRuntime } from './features/runtime/init'; +import { initializeScopes } from './features/scopes'; import { cleanupOldExpandedFolders } from './features/search/utils'; import { variableAdapters } from './features/variables/adapters'; import { createAdHocVariableAdapter } from './features/variables/adhoc/adapter'; @@ -258,6 +259,8 @@ export class GrafanaApp { setReturnToPreviousHook(useReturnToPreviousInternal); setChromeHeaderHeightHook(useChromeHeaderHeight); + initializeScopes(); + const root = createRoot(document.getElementById('reactRoot')!); root.render( createElement(AppWrapper, { diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx index a0e2e3e595f..82f5be705c7 100644 --- a/public/app/core/components/AppChrome/AppChrome.tsx +++ b/public/app/core/components/AppChrome/AppChrome.tsx @@ -9,6 +9,7 @@ import { useGrafana } from 'app/core/context/GrafanaContext'; import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange'; import store from 'app/core/store'; import { CommandPalette } from 'app/features/commandPalette/CommandPalette'; +import { ScopesDashboards } from 'app/features/scopes'; import { KioskMode } from 'app/types'; import { AppChromeMenu } from './AppChromeMenu'; @@ -106,6 +107,7 @@ export function AppChrome({ children }: Props) { {menuDockedAndOpen && ( chrome.setMegaMenuOpen(false)} /> )} + {!state.chromeless && }
+ diff --git a/public/app/core/components/AppChrome/TopBar/TopSearchBarSection.tsx b/public/app/core/components/AppChrome/TopBar/TopSearchBarSection.tsx index ec9176039f0..0ee80b5cdab 100644 --- a/public/app/core/components/AppChrome/TopBar/TopSearchBarSection.tsx +++ b/public/app/core/components/AppChrome/TopBar/TopSearchBarSection.tsx @@ -1,13 +1,12 @@ import { css, cx } from '@emotion/css'; -import { useState } from 'react'; -import * as React from 'react'; +import { ReactNode, useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2, useTheme2 } from '@grafana/ui'; import { useMediaQueryChange } from 'app/core/hooks/useMediaQueryChange'; export interface TopSearchBarSectionProps { - children: React.ReactNode; + children: ReactNode; align?: 'left' | 'center' | 'right'; } diff --git a/public/app/features/dashboard-scene/embedding/EmbeddedDashboard.tsx b/public/app/features/dashboard-scene/embedding/EmbeddedDashboard.tsx index 11a7859df4f..a10c8eae6b9 100644 --- a/public/app/features/dashboard-scene/embedding/EmbeddedDashboard.tsx +++ b/public/app/features/dashboard-scene/embedding/EmbeddedDashboard.tsx @@ -42,7 +42,7 @@ interface RendererProps extends EmbeddedDashboardProps { function EmbeddedDashboardRenderer({ model, initialState, onStateChange }: RendererProps) { const [isActive, setIsActive] = useState(false); - const { controls, body, scopes } = model.useState(); + const { controls, body } = model.useState(); const styles = useStyles2(getStyles); useEffect(() => { @@ -64,12 +64,9 @@ function EmbeddedDashboardRenderer({ model, initialState, onStateChange }: Rende } return ( -
- {scopes && } +
{controls && ( -
+
)} @@ -121,13 +118,6 @@ function getStyles(theme: GrafanaTheme2) { "panels"`, gridTemplateRows: 'auto 1fr', }), - canvasWithScopes: css({ - gridTemplateAreas: ` - "scopes controls" - "panels panels"`, - gridTemplateColumns: `${theme.spacing(32)} 1fr`, - gridTemplateRows: 'auto 1fr', - }), body: css({ label: 'body', flexGrow: 1, @@ -143,8 +133,5 @@ function getStyles(theme: GrafanaTheme2) { gridArea: 'controls', padding: theme.spacing(2, 0, 2, 2), }), - controlsWrapperWithScopes: css({ - padding: theme.spacing(2, 0), - }), }; } diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 201773bd2f6..40cd0b07fc1 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -14,9 +14,9 @@ import { removeDashboardToFetchFromLocalStorage, } from 'app/features/dashboard/state/initDashboard'; import { trackDashboardSceneLoaded } from 'app/features/dashboard/utils/tracking'; +import { getSelectedScopesNames } from 'app/features/scopes'; import { DashboardDTO, DashboardRoutes } from 'app/types'; -import { getScopesFromUrl } from '../../dashboard/utils/getScopesFromUrl'; import { PanelEditor } from '../panel-edit/PanelEditor'; import { DashboardScene } from '../scene/DashboardScene'; import { buildNewDashboardSaveModel } from '../serialization/buildNewDashboardSaveModel'; @@ -299,15 +299,13 @@ export class DashboardScenePageStateManager extends StateManagerBase) { const { vizManager, dataPane, showLibraryPanelSaveModal, showLibraryPanelUnlinkModal } = model.useState(); const { sourcePanel } = vizManager.useState(); const libraryPanel = getLibraryPanel(sourcePanel.resolve()); - const { controls, scopes } = dashboard.useState(); + const { controls } = dashboard.useState(); const styles = useStyles2(getStyles); const { containerProps, primaryProps, secondaryProps, splitterProps, splitterState, onToggleCollapse } = @@ -89,16 +89,9 @@ function VizAndDataPane({ model }: SceneComponentProps) { } return ( -
- {scopes && } +
{controls && ( -
+
)} @@ -163,13 +156,6 @@ function getStyles(theme: GrafanaTheme2) { "panels"`, gridTemplateRows: 'auto 1fr', }), - pageContainerWithScopes: css({ - gridTemplateAreas: ` - "scopes controls" - "panels panels"`, - gridTemplateColumns: `${theme.spacing(32)} 1fr`, - gridTemplateRows: 'auto 1fr', - }), container: css({ gridArea: 'panels', height: '100%', @@ -225,9 +211,6 @@ function getStyles(theme: GrafanaTheme2) { gridArea: 'controls', padding: theme.spacing(2, 0, 2, 2), }), - controlsWrapperWithScopes: css({ - padding: theme.spacing(2, 0), - }), openDataPaneButton: css({ width: theme.spacing(8), justifyContent: 'center', diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 4f39bb8b104..9de61780452 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -34,6 +34,7 @@ import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { deleteDashboard } from 'app/features/manage-dashboards/state/actions'; +import { getClosestScopesFacade, ScopesFacade } from 'app/features/scopes'; import { VariablesChanged } from 'app/features/variables/types'; import { DashboardDTO, DashboardMeta, KioskMode, SaveDashboardResponseDTO } from 'app/types'; import { ShowConfirmModalEvent } from 'app/types/events'; @@ -74,7 +75,6 @@ import { DashboardSceneRenderer } from './DashboardSceneRenderer'; import { DashboardSceneUrlSync } from './DashboardSceneUrlSync'; import { LibraryVizPanel } from './LibraryVizPanel'; import { RowRepeaterBehavior } from './RowRepeaterBehavior'; -import { ScopesScene } from './Scopes/ScopesScene'; import { ViewPanelScene } from './ViewPanelScene'; import { setupKeyboardShortcuts } from './keyboardShortcuts'; @@ -123,8 +123,6 @@ export interface DashboardSceneState extends SceneObjectState { overlay?: SceneObject; /** The dashboard doesn't have panels */ isEmpty?: boolean; - /** Scene object that handles the scopes selector */ - scopes?: ScopesScene; /** Kiosk mode */ kioskMode?: KioskMode; } @@ -163,6 +161,11 @@ export class DashboardScene extends SceneObjectBase { */ private _fromExplore = false; + /** + * A reference to the scopes facade + */ + private _scopesFacade: ScopesFacade | null; + public constructor(state: Partial) { super({ title: 'Dashboard', @@ -170,10 +173,11 @@ export class DashboardScene extends SceneObjectBase { editable: true, body: state.body ?? new SceneFlexLayout({ children: [] }), links: state.links ?? [], - scopes: state.uid && config.featureToggles.scopeFilters ? new ScopesScene() : undefined, ...state, }); + this._scopesFacade = getClosestScopesFacade(this); + this._changeTracker = new DashboardSceneChangeTracker(this); this.addActivationHandler(() => this._activationHandler()); @@ -229,6 +233,9 @@ export class DashboardScene extends SceneObjectBase { // Propagate change edit mode change to children this.propagateEditModeChange(); + // Propagate edit mode to scopes + this._scopesFacade?.enterReadOnly(); + this._changeTracker.startTrackingChanges(); }; @@ -274,6 +281,7 @@ export class DashboardScene extends SceneObjectBase { if (!this.state.isDirty || skipConfirm) { this.exitEditModeConfirmed(restoreInitialState || this.state.isDirty); + this._scopesFacade?.exitReadOnly(); return; } @@ -283,7 +291,10 @@ export class DashboardScene extends SceneObjectBase { text: `You have unsaved changes to this dashboard. Are you sure you want to discard them?`, icon: 'trash-alt', yesText: 'Discard', - onConfirm: this.exitEditModeConfirmed.bind(this), + onConfirm: () => { + this.exitEditModeConfirmed(); + this._scopesFacade?.exitReadOnly(); + }, }) ); } @@ -846,13 +857,13 @@ export class DashboardScene extends SceneObjectBase { dashboardUID: this.state.uid, panelId, panelPluginId: panel?.state.pluginId, - scopes: this.state.scopes?.getSelectedScopes(), + scopes: this._scopesFacade?.value, }; } public enrichFiltersRequest(): Partial { return { - scopes: this.state.scopes?.getSelectedScopes(), + scopes: this._scopesFacade?.value, }; } diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx index a06650ad702..149c884462f 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx @@ -17,8 +17,7 @@ import { DashboardScene } from './DashboardScene'; import { NavToolbarActions } from './NavToolbarActions'; export function DashboardSceneRenderer({ model }: SceneComponentProps) { - const { controls, overlay, editview, editPanel, isEmpty, scopes, meta } = model.useState(); - const { isExpanded: isScopesExpanded } = scopes?.useState() ?? {}; + const { controls, overlay, editview, editPanel, isEmpty, meta } = model.useState(); const styles = useStyles2(getStyles); const location = useLocation(); const navIndex = useSelector((state) => state.navIndex); @@ -60,20 +59,10 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps {editPanel && } {!editPanel && ( -
- {scopes && !meta.dashboardNotFound && } +
{controls && ( -
+
)} @@ -118,18 +107,6 @@ function getStyles(theme: GrafanaTheme2) { "panels"`, gridTemplateRows: 'auto 1fr', }), - pageContainerWithScopes: css({ - gridTemplateAreas: ` - "scopes controls" - "panels panels"`, - gridTemplateColumns: `${theme.spacing(32)} 1fr`, - gridTemplateRows: 'auto 1fr', - }), - pageContainerWithScopesExpanded: css({ - gridTemplateAreas: ` - "scopes controls" - "scopes panels"`, - }), panelsContainer: css({ gridArea: 'panels', }), @@ -143,9 +120,6 @@ function getStyles(theme: GrafanaTheme2) { display: 'none', }, }), - controlsWrapperWithScopes: css({ - padding: theme.spacing(2, 2, 2, 0), - }), canvasContent: css({ label: 'canvas-content', display: 'flex', diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesDashboardsScene.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesDashboardsScene.tsx deleted file mode 100644 index d9bc1cc88d5..00000000000 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesDashboardsScene.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import { css } from '@emotion/css'; -import { Link } from 'react-router-dom'; - -import { GrafanaTheme2, Scope, urlUtil } from '@grafana/data'; -import { SceneComponentProps, SceneObjectBase, SceneObjectState } from '@grafana/scenes'; -import { Button, CustomScrollbar, FilterInput, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; -import { useQueryParams } from 'app/core/hooks/useQueryParams'; -import { t, Trans } from 'app/core/internationalization'; - -import { fetchSuggestedDashboards } from './api'; -import { SuggestedDashboard } from './types'; - -export interface ScopesDashboardsSceneState extends SceneObjectState { - dashboards: SuggestedDashboard[]; - filteredDashboards: SuggestedDashboard[]; - isLoading: boolean; - scopesSelected: boolean; - searchQuery: string; -} - -export class ScopesDashboardsScene extends SceneObjectBase { - static Component = ScopesDashboardsSceneRenderer; - - constructor() { - super({ - dashboards: [], - filteredDashboards: [], - isLoading: false, - scopesSelected: false, - searchQuery: '', - }); - } - - public async fetchDashboards(scopes: Scope[]) { - if (scopes.length === 0) { - return this.setState({ dashboards: [], filteredDashboards: [], isLoading: false, scopesSelected: false }); - } - - this.setState({ isLoading: true }); - - const dashboards = await fetchSuggestedDashboards(scopes); - - this.setState({ - dashboards, - filteredDashboards: this.filterDashboards(dashboards, this.state.searchQuery), - isLoading: false, - scopesSelected: scopes.length > 0, - }); - } - - public changeSearchQuery(searchQuery: string) { - this.setState({ - filteredDashboards: searchQuery - ? this.filterDashboards(this.state.dashboards, searchQuery) - : this.state.dashboards, - searchQuery: searchQuery ?? '', - }); - } - - private filterDashboards(dashboards: SuggestedDashboard[], searchQuery: string): SuggestedDashboard[] { - const lowerCasedSearchQuery = searchQuery.toLowerCase(); - - return dashboards.filter(({ dashboardTitle }) => dashboardTitle.toLowerCase().includes(lowerCasedSearchQuery)); - } -} - -export function ScopesDashboardsSceneRenderer({ model }: SceneComponentProps) { - const { dashboards, filteredDashboards, isLoading, searchQuery, scopesSelected } = model.useState(); - const styles = useStyles2(getStyles); - - const [queryParams] = useQueryParams(); - - if (!isLoading) { - if (!scopesSelected) { - return ( -

- No scopes selected -

- ); - } else if (dashboards.length === 0) { - return ( -

- - No dashboards found for the selected scopes - -

- ); - } - } - - return ( - <> -
- model.changeSearchQuery(value)} - /> -
- - {isLoading ? ( - - ) : filteredDashboards.length > 0 ? ( - - {filteredDashboards.map(({ dashboard, dashboardTitle }) => ( - - {dashboardTitle} - - ))} - - ) : ( -

- No results found for your query - - -

- )} - - ); -} - -const getStyles = (theme: GrafanaTheme2) => { - return { - noResultsContainer: css({ - alignItems: 'center', - display: 'flex', - flexDirection: 'column', - gap: theme.spacing(1), - justifyContent: 'center', - textAlign: 'center', - }), - searchInputContainer: css({ - flex: '0 1 auto', - }), - loadingIndicator: css({ - alignSelf: 'center', - }), - dashboardItem: css({ - padding: theme.spacing(1, 0), - borderBottom: `1px solid ${theme.colors.border.weak}`, - - '& :is(:first-child)': { - paddingTop: 0, - }, - }), - }; -}; diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.tsx deleted file mode 100644 index e661f703950..00000000000 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { css, cx } from '@emotion/css'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { SceneComponentProps, sceneGraph, SceneObjectBase, SceneObjectState } from '@grafana/scenes'; -import { IconButton, useStyles2 } from '@grafana/ui'; -import { t } from 'app/core/internationalization'; - -import { ScopesDashboardsScene } from './ScopesDashboardsScene'; -import { ScopesFiltersScene } from './ScopesFiltersScene'; - -export interface ScopesSceneState extends SceneObjectState { - dashboards: ScopesDashboardsScene; - filters: ScopesFiltersScene; - isExpanded: boolean; - isViewing: boolean; -} - -export class ScopesScene extends SceneObjectBase { - static Component = ScopesSceneRenderer; - - constructor() { - super({ - dashboards: new ScopesDashboardsScene(), - filters: new ScopesFiltersScene(), - isExpanded: false, - isViewing: false, - }); - - this.addActivationHandler(() => { - this._subs.add( - this.state.filters.subscribeToState((newState, prevState) => { - if (!newState.isLoadingScopes && newState.scopes !== prevState.scopes) { - if (this.state.isExpanded) { - this.state.dashboards.fetchDashboards(this.state.filters.getSelectedScopes()); - } - - sceneGraph.getTimeRange(this.parent!).onRefresh(); - } - }) - ); - - this._subs.add( - this.parent?.subscribeToState((newState) => { - const isEditing = 'isEditing' in newState ? !!newState.isEditing : false; - - if (isEditing !== this.state.isViewing) { - if (isEditing) { - this.enterViewMode(); - } else { - this.exitViewMode(); - } - } - }) - ); - }); - } - - public getSelectedScopes() { - return this.state.filters.getSelectedScopes(); - } - - public toggleIsExpanded() { - const isExpanded = !this.state.isExpanded; - - if (isExpanded) { - this.state.dashboards.fetchDashboards(this.getSelectedScopes()); - } - - this.setState({ isExpanded }); - } - - private enterViewMode() { - this.setState({ isExpanded: false, isViewing: true }); - - this.state.filters.enterViewMode(); - } - - private exitViewMode() { - this.setState({ isViewing: false }); - } -} - -export function ScopesSceneRenderer({ model }: SceneComponentProps) { - const { filters, dashboards, isExpanded, isViewing } = model.useState(); - const styles = useStyles2(getStyles); - - return ( -
-
- {!isViewing && ( - model.toggleIsExpanded()} - /> - )} - -
- - {isExpanded && !isViewing && ( -
- -
- )} -
- ); -} - -const getStyles = (theme: GrafanaTheme2) => { - return { - container: css({ - display: 'flex', - flexDirection: 'column', - gridArea: 'scopes', - }), - containerExpanded: css({ - backgroundColor: theme.colors.background.primary, - height: '100%', - }), - filtersContainer: css({ - display: 'flex', - flex: '0 1 auto', - flexDirection: 'row', - padding: theme.spacing(2, 2, 2, 2), - }), - filtersContainerExpanded: css({ - borderBottom: `1px solid ${theme.colors.border.weak}`, - padding: theme.spacing(2), - }), - iconNotExpanded: css({ - transform: 'scaleX(-1)', - }), - dashboardsContainer: css({ - display: 'flex', - flex: '1 1 auto', - flexDirection: 'column', - gap: theme.spacing(3), - overflow: 'hidden', - padding: theme.spacing(2), - }), - }; -}; diff --git a/public/app/features/dashboard-scene/scene/Scopes/utils.ts b/public/app/features/dashboard-scene/scene/Scopes/utils.ts deleted file mode 100644 index bbe68774966..00000000000 --- a/public/app/features/dashboard-scene/scene/Scopes/utils.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Scope } from '@grafana/data'; - -export function getBasicScope(name: string): Scope { - return { - metadata: { name }, - spec: { - filters: [], - title: name, - type: '', - category: '', - description: '', - }, - }; -} - -export function mergeScopes(scope1: Scope, scope2: Scope): Scope { - return { - ...scope1, - metadata: { - ...scope1.metadata, - ...scope2.metadata, - }, - spec: { - ...scope1.spec, - ...scope2.spec, - }, - }; -} diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index 5d45e4a4aa0..d03b55af774 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -28,8 +28,10 @@ import { UserActionEvent, GroupByVariable, AdHocFiltersVariable, + sceneGraph, } from '@grafana/scenes'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; +import { ScopesFacade } from 'app/features/scopes'; import { DashboardDTO, DashboardDataDTO } from 'app/types'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; @@ -281,6 +283,9 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, registerPanelInteractionsReporter, new behaviors.LiveNowTimer({ enabled: oldModel.liveNow }), preserveDashboardSceneStateInLocalStorage, + new ScopesFacade({ + handler: (facade) => sceneGraph.getTimeRange(facade).onRefresh(), + }), ], $data: new DashboardDataLayerSet({ annotationLayers, alertStatesLayer }), controls: new DashboardControls({ diff --git a/public/app/features/dashboard/api/dashboard_api.ts b/public/app/features/dashboard/api/dashboard_api.ts index 477495fda96..661a02379a8 100644 --- a/public/app/features/dashboard/api/dashboard_api.ts +++ b/public/app/features/dashboard/api/dashboard_api.ts @@ -10,10 +10,9 @@ import { import { SaveDashboardCommand } from 'app/features/dashboard/components/SaveDashboard/types'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { DeleteDashboardResponse } from 'app/features/manage-dashboards/types'; +import { getSelectedScopesNames } from 'app/features/scopes'; import { DashboardDTO, DashboardDataDTO, SaveDashboardResponseDTO } from 'app/types'; -import { getScopesFromUrl } from '../utils/getScopesFromUrl'; - export interface DashboardAPI { /** Get a dashboard with the access control metadata */ getDashboardDTO(uid: string): Promise; @@ -43,8 +42,7 @@ class LegacyDashboardAPI implements DashboardAPI { } getDashboardDTO(uid: string): Promise { - const scopesSearchParams = getScopesFromUrl(); - const scopes = scopesSearchParams?.getAll('scopes') ?? []; + const scopes = getSelectedScopesNames(); const queryParams = scopes.length > 0 ? { scopes } : undefined; return getBackendSrv().get(`/api/dashboards/uid/${uid}`, queryParams); diff --git a/public/app/features/dashboard/utils/getScopesFromUrl.ts b/public/app/features/dashboard/utils/getScopesFromUrl.ts deleted file mode 100644 index e9539037fe5..00000000000 --- a/public/app/features/dashboard/utils/getScopesFromUrl.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { config, locationService } from '@grafana/runtime'; - -export function getScopesFromUrl(): URLSearchParams | undefined { - if (!config.featureToggles.scopeFilters || !config.featureToggles.passScopeToDashboardApi) { - return undefined; - } - - const queryParams = locationService.getSearchObject(); - const rawScopes = queryParams['scopes'] ?? []; - const scopes = Array.isArray(rawScopes) ? rawScopes : [rawScopes]; - - return new URLSearchParams(scopes.map((scope) => ['scopes', String(scope)])); -} diff --git a/public/app/features/scopes/ScopesDashboards.tsx b/public/app/features/scopes/ScopesDashboards.tsx new file mode 100644 index 00000000000..e3b5227af86 --- /dev/null +++ b/public/app/features/scopes/ScopesDashboards.tsx @@ -0,0 +1,9 @@ +import { scopesDashboardsScene } from './instance'; + +export function ScopesDashboards() { + if (!scopesDashboardsScene) { + return null; + } + + return ; +} diff --git a/public/app/features/scopes/ScopesFacadeScene.ts b/public/app/features/scopes/ScopesFacadeScene.ts new file mode 100644 index 00000000000..db65da5f7a8 --- /dev/null +++ b/public/app/features/scopes/ScopesFacadeScene.ts @@ -0,0 +1,53 @@ +import { SceneObjectBase, SceneObjectState } from '@grafana/scenes'; + +import { scopesSelectorScene } from './instance'; +import { disableScopes, enableScopes, enterScopesReadOnly, exitScopesReadOnly, getSelectedScopes } from './utils'; + +interface ScopesFacadeState extends SceneObjectState { + // A callback that will be executed when new scopes are set + handler?: (facade: ScopesFacade) => void; +} + +export class ScopesFacade extends SceneObjectBase { + public constructor(state: ScopesFacadeState) { + super(state); + + this.addActivationHandler(this._activationHandler); + } + + private _activationHandler = () => { + this.enable(); + + this._subs.add( + scopesSelectorScene?.subscribeToState((newState, prevState) => { + if (!newState.isLoadingScopes && (prevState.isLoadingScopes || newState.scopes !== prevState.scopes)) { + this.state.handler?.(this); + } + }) + ); + + return () => { + this.disable(); + }; + }; + + public get value() { + return getSelectedScopes(); + } + + public enable() { + enableScopes(); + } + + public disable() { + disableScopes(); + } + + public enterReadOnly() { + enterScopesReadOnly(); + } + + public exitReadOnly() { + exitScopesReadOnly(); + } +} diff --git a/public/app/features/scopes/ScopesSelector.tsx b/public/app/features/scopes/ScopesSelector.tsx new file mode 100644 index 00000000000..b7b4080d78b --- /dev/null +++ b/public/app/features/scopes/ScopesSelector.tsx @@ -0,0 +1,9 @@ +import { scopesSelectorScene } from './instance'; + +export function ScopesSelector() { + if (!scopesSelectorScene) { + return null; + } + + return ; +} diff --git a/public/app/features/scopes/index.ts b/public/app/features/scopes/index.ts new file mode 100644 index 00000000000..ad822091ddb --- /dev/null +++ b/public/app/features/scopes/index.ts @@ -0,0 +1,14 @@ +export { initializeScopes } from './instance'; +export { ScopesDashboards } from './ScopesDashboards'; +/* eslint-disable */ +export { ScopesFacade } from './ScopesFacadeScene'; +export { ScopesSelector } from './ScopesSelector'; +export { + disableScopes, + enableScopes, + enterScopesReadOnly, + exitScopesReadOnly, + getClosestScopesFacade, + getSelectedScopes, + getSelectedScopesNames, +} from './utils'; diff --git a/public/app/features/scopes/instance.tsx b/public/app/features/scopes/instance.tsx new file mode 100644 index 00000000000..b1c1709b0d1 --- /dev/null +++ b/public/app/features/scopes/instance.tsx @@ -0,0 +1,21 @@ +import { config } from '@grafana/runtime'; +import { UrlSyncManager } from '@grafana/scenes'; + +import { ScopesDashboardsScene } from './internal/ScopesDashboardsScene'; +import { ScopesSelectorScene } from './internal/ScopesSelectorScene'; + +export let scopesDashboardsScene: ScopesDashboardsScene | null = null; +export let scopesSelectorScene: ScopesSelectorScene | null = null; + +export function initializeScopes() { + if (config.featureToggles.scopeFilters) { + scopesSelectorScene = new ScopesSelectorScene(); + scopesDashboardsScene = new ScopesDashboardsScene(); + + scopesSelectorScene.setState({ dashboards: scopesDashboardsScene.getRef() }); + scopesDashboardsScene.setState({ selector: scopesSelectorScene.getRef() }); + + const urlSyncManager = new UrlSyncManager(); + urlSyncManager.initSync(scopesSelectorScene!); + } +} diff --git a/public/app/features/scopes/internal/ScopesDashboardsScene.tsx b/public/app/features/scopes/internal/ScopesDashboardsScene.tsx new file mode 100644 index 00000000000..eda28702643 --- /dev/null +++ b/public/app/features/scopes/internal/ScopesDashboardsScene.tsx @@ -0,0 +1,262 @@ +import { css, cx } from '@emotion/css'; +import { isEqual } from 'lodash'; +import { Link } from 'react-router-dom'; + +import { GrafanaTheme2, urlUtil } from '@grafana/data'; +import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState } from '@grafana/scenes'; +import { Button, CustomScrollbar, FilterInput, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; +import { useQueryParams } from 'app/core/hooks/useQueryParams'; +import { t, Trans } from 'app/core/internationalization'; + +import { ScopesSelectorScene } from './ScopesSelectorScene'; +import { fetchSuggestedDashboards } from './api'; +import { DASHBOARDS_OPENED_KEY } from './const'; +import { SuggestedDashboard } from './types'; +import { getScopeNamesFromSelectedScopes } from './utils'; + +export interface ScopesDashboardsSceneState extends SceneObjectState { + selector: SceneObjectRef | null; + dashboards: SuggestedDashboard[]; + filteredDashboards: SuggestedDashboard[]; + forScopeNames: string[]; + isLoading: boolean; + isPanelOpened: boolean; + isEnabled: boolean; + scopesSelected: boolean; + searchQuery: string; +} + +export const getInitialDashboardsState: () => Omit = () => ({ + dashboards: [], + filteredDashboards: [], + forScopeNames: [], + isLoading: false, + isPanelOpened: localStorage.getItem(DASHBOARDS_OPENED_KEY) === 'true', + isEnabled: false, + scopesSelected: false, + searchQuery: '', +}); + +export class ScopesDashboardsScene extends SceneObjectBase { + static Component = ScopesDashboardsSceneRenderer; + + constructor() { + super({ + selector: null, + ...getInitialDashboardsState(), + }); + + this.addActivationHandler(() => { + if (this.state.isPanelOpened) { + this.fetchDashboards(); + } + + const resolvedSelector = this.state.selector?.resolve(); + + if (resolvedSelector) { + this._subs.add( + resolvedSelector.subscribeToState((newState, prevState) => { + if ( + this.state.isPanelOpened && + !newState.isLoadingScopes && + (prevState.isLoadingScopes || newState.scopes !== prevState.scopes) + ) { + this.fetchDashboards(); + } + }) + ); + } + }); + } + + public async fetchDashboards() { + const scopeNames = getScopeNamesFromSelectedScopes(this.state.selector?.resolve().state.scopes ?? []); + + if (isEqual(scopeNames, this.state.forScopeNames)) { + return; + } + + if (scopeNames.length === 0) { + return this.setState({ + dashboards: [], + filteredDashboards: [], + forScopeNames: [], + isLoading: false, + scopesSelected: false, + }); + } + + this.setState({ isLoading: true }); + + const dashboards = await fetchSuggestedDashboards(scopeNames); + + this.setState({ + dashboards, + filteredDashboards: this.filterDashboards(dashboards, this.state.searchQuery), + forScopeNames: scopeNames, + isLoading: false, + scopesSelected: scopeNames.length > 0, + }); + } + + public changeSearchQuery(searchQuery: string) { + this.setState({ + filteredDashboards: searchQuery + ? this.filterDashboards(this.state.dashboards, searchQuery) + : this.state.dashboards, + searchQuery: searchQuery ?? '', + }); + } + + public togglePanel() { + if (this.state.isPanelOpened) { + this.closePanel(); + } else { + this.openPanel(); + } + } + + public openPanel() { + this.fetchDashboards(); + this.setState({ isPanelOpened: true }); + localStorage.setItem(DASHBOARDS_OPENED_KEY, JSON.stringify(true)); + } + + public closePanel() { + this.setState({ isPanelOpened: false }); + localStorage.setItem(DASHBOARDS_OPENED_KEY, JSON.stringify(false)); + } + + public enable() { + this.setState({ isEnabled: true }); + } + + public disable() { + this.setState({ isEnabled: false }); + } + + private filterDashboards(dashboards: SuggestedDashboard[], searchQuery: string): SuggestedDashboard[] { + const lowerCasedSearchQuery = searchQuery.toLowerCase(); + + return dashboards.filter(({ dashboardTitle }) => dashboardTitle.toLowerCase().includes(lowerCasedSearchQuery)); + } +} + +export function ScopesDashboardsSceneRenderer({ model }: SceneComponentProps) { + const { dashboards, filteredDashboards, isLoading, isPanelOpened, isEnabled, searchQuery, scopesSelected } = + model.useState(); + const styles = useStyles2(getStyles); + + const [queryParams] = useQueryParams(); + + if (!isEnabled || !isPanelOpened) { + return null; + } + + if (!isLoading) { + if (!scopesSelected) { + return ( +
+ No scopes selected +
+ ); + } else if (dashboards.length === 0) { + return ( +
+ No dashboards found for the selected scopes +
+ ); + } + } + + return ( +
+
+ model.changeSearchQuery(value)} + /> +
+ + {isLoading ? ( + + ) : filteredDashboards.length > 0 ? ( + + {filteredDashboards.map(({ dashboard, dashboardTitle }) => ( + + {dashboardTitle} + + ))} + + ) : ( +

+ No results found for your query + + +

+ )} +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + container: css({ + backgroundColor: theme.colors.background.primary, + borderRight: `1px solid ${theme.colors.border.weak}`, + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), + padding: theme.spacing(2), + width: theme.spacing(37.5), + }), + noResultsContainer: css({ + alignItems: 'center', + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(1), + height: '100%', + justifyContent: 'center', + margin: 0, + textAlign: 'center', + }), + searchInputContainer: css({ + flex: '0 1 auto', + }), + loadingIndicator: css({ + alignSelf: 'center', + }), + dashboardItem: css({ + padding: theme.spacing(1, 0), + borderBottom: `1px solid ${theme.colors.border.weak}`, + + '& :is(:first-child)': { + paddingTop: 0, + }, + }), + }; +}; diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesInput.tsx b/public/app/features/scopes/internal/ScopesInput.tsx similarity index 80% rename from public/app/features/dashboard-scene/scene/Scopes/ScopesInput.tsx rename to public/app/features/scopes/internal/ScopesInput.tsx index 9e4e3d0e9bc..e440d38741f 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesInput.tsx +++ b/public/app/features/scopes/internal/ScopesInput.tsx @@ -40,20 +40,21 @@ export function ScopesInput({ let titles: string[]; if (path.length > 0) { - titles = path - .map((nodeName) => { - const cl = currentLevel[nodeName]; - if (!cl) { - return null; - } + titles = path.reduce((acc, nodeName) => { + const cl = currentLevel[nodeName]; - const { title, nodes } = cl; + if (!cl) { + return acc; + } + + const { title, nodes } = cl; + + currentLevel = nodes; - currentLevel = nodes; + acc.push(title); - return title; - }) - .filter((title) => title !== null) as string[]; + return acc; + }, []); if (titles[0] === '') { titles.splice(0, 1); @@ -90,15 +91,16 @@ export function ScopesInput({ () => ( 0 && !isDisabled ? ( onRemoveAllClick()} /> @@ -127,8 +129,8 @@ const getStyles = (theme: GrafanaTheme2) => { return { scopePath: css({ color: theme.colors.text.primary, - fontSize: theme.typography.pxToRem(14), - margin: theme.spacing(1, 0), + fontSize: theme.typography.pxToRem(12), + margin: theme.spacing(0, 0), }), }; }; diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx b/public/app/features/scopes/internal/ScopesSelectorScene.tsx similarity index 63% rename from public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx rename to public/app/features/scopes/internal/ScopesSelectorScene.tsx index d4dffcfa466..e9e5f0166c8 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx +++ b/public/app/features/scopes/internal/ScopesSelectorScene.tsx @@ -2,66 +2,72 @@ import { css } from '@emotion/css'; import { isEqual } from 'lodash'; import { finalize, from, Subscription } from 'rxjs'; -import { GrafanaTheme2, Scope } from '@grafana/data'; +import { GrafanaTheme2 } from '@grafana/data'; import { SceneComponentProps, - sceneGraph, SceneObjectBase, + SceneObjectRef, SceneObjectState, SceneObjectUrlSyncConfig, SceneObjectUrlValues, SceneObjectWithUrlSync, } from '@grafana/scenes'; -import { Button, Drawer, Spinner, useStyles2 } from '@grafana/ui'; +import { Button, Drawer, IconButton, Spinner, useStyles2 } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; +import { ScopesDashboardsScene } from './ScopesDashboardsScene'; import { ScopesInput } from './ScopesInput'; -import { ScopesScene } from './ScopesScene'; import { ScopesTree } from './ScopesTree'; import { fetchNodes, fetchScope, fetchSelectedScopes } from './api'; import { NodeReason, NodesMap, SelectedScope, TreeScope } from './types'; -import { getBasicScope } from './utils'; +import { getBasicScope, getScopeNamesFromSelectedScopes, getTreeScopesFromSelectedScopes } from './utils'; -export interface ScopesFiltersSceneState extends SceneObjectState { +export interface ScopesSelectorSceneState extends SceneObjectState { + dashboards: SceneObjectRef | null; nodes: NodesMap; loadingNodeName: string | undefined; scopes: SelectedScope[]; treeScopes: TreeScope[]; + isReadOnly: boolean; isLoadingScopes: boolean; - isOpened: boolean; + isPickerOpened: boolean; + isEnabled: boolean; } -export class ScopesFiltersScene extends SceneObjectBase implements SceneObjectWithUrlSync { - static Component = ScopesFiltersSceneRenderer; +export const initialSelectorState: Omit = { + nodes: { + '': { + name: '', + reason: NodeReason.Result, + nodeType: 'container', + title: '', + isExpandable: true, + isSelectable: false, + isExpanded: true, + query: '', + nodes: {}, + }, + }, + loadingNodeName: undefined, + scopes: [], + treeScopes: [], + isReadOnly: false, + isLoadingScopes: false, + isPickerOpened: false, + isEnabled: false, +}; + +export class ScopesSelectorScene extends SceneObjectBase implements SceneObjectWithUrlSync { + static Component = ScopesSelectorSceneRenderer; protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['scopes'] }); private nodesFetchingSub: Subscription | undefined; - get scopesParent(): ScopesScene { - return sceneGraph.getAncestor(this, ScopesScene); - } - constructor() { super({ - nodes: { - '': { - name: '', - reason: NodeReason.Result, - nodeType: 'container', - title: '', - isExpandable: true, - isSelectable: false, - isExpanded: true, - query: '', - nodes: {}, - }, - }, - loadingNodeName: undefined, - scopes: [], - treeScopes: [], - isLoadingScopes: false, - isOpened: false, + dashboards: null, + ...initialSelectorState, }); this.addActivationHandler(() => { @@ -75,7 +81,7 @@ export class ScopesFiltersScene extends SceneObjectBase public getUrlState() { return { - scopes: this.state.scopes.map(({ scope }) => scope.metadata.name), + scopes: this.state.isEnabled ? getScopeNamesFromSelectedScopes(this.state.scopes) : [], }; } @@ -177,8 +183,8 @@ export class ScopesFiltersScene extends SceneObjectBase } } - public open() { - if (!this.scopesParent.state.isViewing) { + public openPicker() { + if (!this.state.isReadOnly) { let nodes = { ...this.state.nodes }; // First close all nodes @@ -191,20 +197,16 @@ export class ScopesFiltersScene extends SceneObjectBase // Expand the nodes to the selected scope nodes = this.expandNodes(nodes, path); - this.setState({ isOpened: true, nodes }); + this.setState({ isPickerOpened: true, nodes }); } } - public close() { - this.setState({ isOpened: false }); - } - - public getSelectedScopes(): Scope[] { - return this.state.scopes.map(({ scope }) => scope); + public closePicker() { + this.setState({ isPickerOpened: false }); } public async updateScopes(treeScopes = this.state.treeScopes) { - if (isEqual(treeScopes, this.getTreeScopes())) { + if (isEqual(treeScopes, getTreeScopesFromSelectedScopes(this.state.scopes))) { return; } @@ -221,15 +223,27 @@ export class ScopesFiltersScene extends SceneObjectBase } public resetDirtyScopeNames() { - this.setState({ treeScopes: this.getTreeScopes() }); + this.setState({ treeScopes: getTreeScopesFromSelectedScopes(this.state.scopes) }); } public removeAllScopes() { this.setState({ scopes: [], treeScopes: [], isLoadingScopes: false }); } - public enterViewMode() { - this.setState({ isOpened: false }); + public enterReadOnly() { + this.setState({ isReadOnly: true, isPickerOpened: false }); + } + + public exitReadOnly() { + this.setState({ isReadOnly: false }); + } + + public enable() { + this.setState({ isEnabled: true }); + } + + public disable() { + this.setState({ isEnabled: false }); } private closeNodes(nodes: NodesMap): NodesMap { @@ -260,42 +274,68 @@ export class ScopesFiltersScene extends SceneObjectBase return nodes; } - - private getTreeScopes(): TreeScope[] { - return this.state.scopes.map(({ scope, path }) => ({ - scopeName: scope.metadata.name, - path, - })); - } } -export function ScopesFiltersSceneRenderer({ model }: SceneComponentProps) { +export function ScopesSelectorSceneRenderer({ model }: SceneComponentProps) { const styles = useStyles2(getStyles); - const { nodes, loadingNodeName, treeScopes, isLoadingScopes, isOpened, scopes } = model.useState(); - const { isViewing } = model.scopesParent.useState(); + const { + dashboards: dashboardsRef, + nodes, + loadingNodeName, + scopes, + treeScopes, + isReadOnly, + isLoadingScopes, + isPickerOpened, + isEnabled, + } = model.useState(); + + const dashboards = dashboardsRef?.resolve(); + + const { isPanelOpened: isDashboardsPanelOpened } = dashboards?.useState() ?? {}; + + if (!isEnabled) { + return null; + } + + const dashboardsIconLabel = isReadOnly + ? t('scopes.dashboards.toggle.disabled', 'Suggested dashboards list is disabled due to read only mode') + : isDashboardsPanelOpened + ? t('scopes.dashboards.toggle.collapse', 'Collapse suggested dashboards list') + : t('scopes.dashboards.toggle..expand', 'Expand suggested dashboards list'); return ( - <> +
+ dashboards?.togglePanel()} + /> + model.open()} + onInputClick={() => model.openPicker()} onRemoveAllClick={() => model.removeAllScopes()} /> - {isOpened && ( + {isPickerOpened && ( { - model.close(); + model.closePicker(); model.resetDirtyScopeNames(); }} > {isLoadingScopes ? ( - + ) : (
)} - +
); } const getStyles = (theme: GrafanaTheme2) => { return { + container: css({ + borderLeft: `1px solid ${theme.colors.border.weak}`, + display: 'flex', + flexDirection: 'row', + paddingLeft: theme.spacing(2), + }), + dashboards: css({ + color: theme.colors.text.secondary, + marginRight: theme.spacing(2), + + '&:hover': css({ + color: theme.colors.text.primary, + }), + }), buttonGroup: css({ display: 'flex', gap: theme.spacing(1), diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesTree.tsx b/public/app/features/scopes/internal/ScopesTree.tsx similarity index 100% rename from public/app/features/dashboard-scene/scene/Scopes/ScopesTree.tsx rename to public/app/features/scopes/internal/ScopesTree.tsx diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeHeadline.tsx b/public/app/features/scopes/internal/ScopesTreeHeadline.tsx similarity index 100% rename from public/app/features/dashboard-scene/scene/Scopes/ScopesTreeHeadline.tsx rename to public/app/features/scopes/internal/ScopesTreeHeadline.tsx diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeItem.tsx b/public/app/features/scopes/internal/ScopesTreeItem.tsx similarity index 100% rename from public/app/features/dashboard-scene/scene/Scopes/ScopesTreeItem.tsx rename to public/app/features/scopes/internal/ScopesTreeItem.tsx diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLoading.tsx b/public/app/features/scopes/internal/ScopesTreeLoading.tsx similarity index 100% rename from public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLoading.tsx rename to public/app/features/scopes/internal/ScopesTreeLoading.tsx diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeSearch.tsx b/public/app/features/scopes/internal/ScopesTreeSearch.tsx similarity index 100% rename from public/app/features/dashboard-scene/scene/Scopes/ScopesTreeSearch.tsx rename to public/app/features/scopes/internal/ScopesTreeSearch.tsx diff --git a/public/app/features/dashboard-scene/scene/Scopes/api.ts b/public/app/features/scopes/internal/api.ts similarity index 91% rename from public/app/features/dashboard-scene/scene/Scopes/api.ts rename to public/app/features/scopes/internal/api.ts index d1deda21d73..634de927253 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/api.ts +++ b/public/app/features/scopes/internal/api.ts @@ -87,10 +87,10 @@ export async function fetchSelectedScopes(treeScopes: TreeScope[]): Promise { +export async function fetchDashboards(scopeNames: string[]): Promise { try { const response = await getBackendSrv().get<{ items: ScopeDashboardBinding[] }>(dashboardsEndpoint, { - scope: scopes.map(({ metadata: { name } }) => name), + scope: scopeNames, }); return response?.items ?? []; @@ -99,8 +99,8 @@ export async function fetchDashboards(scopes: Scope[]): Promise { - const items = await fetchDashboards(scopes); +export async function fetchSuggestedDashboards(scopeNames: string[]): Promise { + const items = await fetchDashboards(scopeNames); return Object.values( items.reduce>((acc, item) => { diff --git a/public/app/features/scopes/internal/const.ts b/public/app/features/scopes/internal/const.ts new file mode 100644 index 00000000000..643a106960d --- /dev/null +++ b/public/app/features/scopes/internal/const.ts @@ -0,0 +1 @@ +export const DASHBOARDS_OPENED_KEY = 'grafana.scopes.dashboards.opened'; diff --git a/public/app/features/dashboard-scene/scene/Scopes/types.ts b/public/app/features/scopes/internal/types.ts similarity index 100% rename from public/app/features/dashboard-scene/scene/Scopes/types.ts rename to public/app/features/scopes/internal/types.ts diff --git a/public/app/features/scopes/internal/utils.ts b/public/app/features/scopes/internal/utils.ts new file mode 100644 index 00000000000..5052469ac48 --- /dev/null +++ b/public/app/features/scopes/internal/utils.ts @@ -0,0 +1,45 @@ +import { Scope } from '@grafana/data'; + +import { SelectedScope, TreeScope } from './types'; + +export function getBasicScope(name: string): Scope { + return { + metadata: { name }, + spec: { + filters: [], + title: name, + type: '', + category: '', + description: '', + }, + }; +} + +export function mergeScopes(scope1: Scope, scope2: Scope): Scope { + return { + ...scope1, + metadata: { + ...scope1.metadata, + ...scope2.metadata, + }, + spec: { + ...scope1.spec, + ...scope2.spec, + }, + }; +} + +export function getTreeScopesFromSelectedScopes(scopes: SelectedScope[]): TreeScope[] { + return scopes.map(({ scope, path }) => ({ + scopeName: scope.metadata.name, + path, + })); +} + +export function getScopesFromSelectedScopes(scopes: SelectedScope[]): Scope[] { + return scopes.map(({ scope }) => scope); +} + +export function getScopeNamesFromSelectedScopes(scopes: SelectedScope[]): string[] { + return scopes.map(({ scope }) => scope.metadata.name); +} diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx b/public/app/features/scopes/scopes.test.tsx similarity index 77% rename from public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx rename to public/app/features/scopes/scopes.test.tsx index 0f4fb4d188e..1b8d9116609 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx +++ b/public/app/features/scopes/scopes.test.tsx @@ -6,8 +6,7 @@ import { sceneGraph } from '@grafana/scenes'; import { getDashboardAPI, setDashboardAPI } from 'app/features/dashboard/api/dashboard_api'; import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; -import { ScopesFiltersScene } from './ScopesFiltersScene'; -import { ScopesScene } from './ScopesScene'; +import { initializeScopes, scopesDashboardsScene, scopesSelectorScene } from './instance'; import { buildTestScene, fetchNodesSpy, @@ -15,12 +14,8 @@ import { fetchSelectedScopesSpy, fetchSuggestedDashboardsSpy, getDashboard, - getDashboardsContainer, getDashboardsExpand, getDashboardsSearch, - getFiltersApply, - getFiltersCancel, - getFiltersInput, getMock, getNotFoundForFilter, getNotFoundForFilterClear, @@ -43,22 +38,26 @@ import { getResultClustersSlothClusterEastRadio, getResultClustersSlothClusterNorthRadio, getResultClustersSlothClusterSouthRadio, + getSelectorApply, + getSelectorCancel, + getSelectorInput, getTreeHeadline, getTreeSearch, mocksScopes, queryAllDashboard, queryDashboard, queryDashboardsContainer, - queryDashboardsExpand, queryDashboardsSearch, - queryFiltersApply, queryPersistedApplicationsSlothPictureFactoryTitle, queryPersistedApplicationsSlothVoteTrackerTitle, queryResultApplicationsClustersTitle, queryResultApplicationsSlothPictureFactoryTitle, queryResultApplicationsSlothVoteTrackerTitle, + querySelectorApply, renderDashboard, + resetScenes, } from './testUtils'; +import { getClosestScopesFacade } from './utils'; jest.mock('@grafana/runtime', () => ({ __esModule: true, @@ -66,25 +65,26 @@ jest.mock('@grafana/runtime', () => ({ getBackendSrv: () => ({ get: getMock, }), + usePluginLinkExtensions: jest.fn().mockReturnValue({ extensions: [] }), })); -describe('ScopesScene', () => { +describe('Scopes', () => { describe('Feature flag off', () => { beforeAll(() => { config.featureToggles.scopeFilters = false; + + initializeScopes(); }); it('Does not initialize', () => { const dashboardScene = buildTestScene(); dashboardScene.activate(); - expect(dashboardScene.state.scopes).toBeUndefined(); + expect(scopesSelectorScene).toBeNull(); }); }); describe('Feature flag on', () => { let dashboardScene: DashboardScene; - let scopesScene: ScopesScene; - let filtersScene: ScopesFiltersScene; beforeAll(() => { config.featureToggles.scopeFilters = true; @@ -99,27 +99,28 @@ describe('ScopesScene', () => { fetchSuggestedDashboardsSpy.mockClear(); getMock.mockClear(); + initializeScopes(); + dashboardScene = buildTestScene(); - scopesScene = dashboardScene.state.scopes!; - filtersScene = scopesScene.state.filters; renderDashboard(dashboardScene); }); afterEach(() => { + resetScenes(); cleanup(); }); describe('Tree', () => { it('Navigates through scopes nodes', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsClustersExpand()); await userEvents.click(getResultApplicationsExpand()); }); it('Fetches scope details on select', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothVoteTrackerSelect()); await waitFor(() => expect(fetchScopeSpy).toHaveBeenCalledTimes(1)); @@ -127,49 +128,49 @@ describe('ScopesScene', () => { it('Selects the proper scopes', async () => { await act(async () => - filtersScene.updateScopes([ + scopesSelectorScene?.updateScopes([ { scopeName: 'slothPictureFactory', path: [] }, { scopeName: 'slothVoteTracker', path: [] }, ]) ); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); expect(getResultApplicationsSlothVoteTrackerSelect()).toBeChecked(); expect(getResultApplicationsSlothPictureFactorySelect()).toBeChecked(); }); it('Can select scopes from same level', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothVoteTrackerSelect()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); await userEvents.click(getResultApplicationsClustersSelect()); - await userEvents.click(getFiltersApply()); - expect(getFiltersInput().value).toBe('slothVoteTracker, slothPictureFactory, Cluster Index Helper'); + await userEvents.click(getSelectorApply()); + expect(getSelectorInput().value).toBe('slothVoteTracker, slothPictureFactory, Cluster Index Helper'); }); it('Can select a node from an inner level', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothVoteTrackerSelect()); await userEvents.click(getResultApplicationsClustersExpand()); await userEvents.click(getResultApplicationsClustersSlothClusterNorthSelect()); - await userEvents.click(getFiltersApply()); - expect(getFiltersInput().value).toBe('slothClusterNorth'); + await userEvents.click(getSelectorApply()); + expect(getSelectorInput().value).toBe('slothClusterNorth'); }); it('Can select a node from an upper level', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothVoteTrackerSelect()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultClustersSelect()); - await userEvents.click(getFiltersApply()); - expect(getFiltersInput().value).toBe('Cluster Index Helper'); + await userEvents.click(getSelectorApply()); + expect(getSelectorInput().value).toBe('Cluster Index Helper'); }); it('Respects only one select per container', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultClustersExpand()); await userEvents.click(getResultClustersSlothClusterNorthRadio()); expect(getResultClustersSlothClusterNorthRadio().checked).toBe(true); @@ -180,7 +181,7 @@ describe('ScopesScene', () => { }); it('Search works', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.type(getTreeSearch(), 'Clusters'); await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3)); @@ -196,18 +197,18 @@ describe('ScopesScene', () => { }); it('Opens to a selected scope', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultClustersExpand()); - await userEvents.click(getFiltersApply()); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorApply()); + await userEvents.click(getSelectorInput()); expect(queryResultApplicationsSlothPictureFactoryTitle()).toBeInTheDocument(); }); it('Persists a scope', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); await userEvents.type(getTreeSearch(), 'slothVoteTracker'); @@ -219,7 +220,7 @@ describe('ScopesScene', () => { }); it('Does not persist a retrieved scope', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); await userEvents.type(getTreeSearch(), 'slothPictureFactory'); @@ -229,7 +230,7 @@ describe('ScopesScene', () => { }); it('Removes persisted nodes', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); await userEvents.type(getTreeSearch(), 'slothVoteTracker'); @@ -243,7 +244,7 @@ describe('ScopesScene', () => { }); it('Persists nodes from search', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.type(getTreeSearch(), 'sloth'); await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3)); @@ -260,33 +261,33 @@ describe('ScopesScene', () => { }); it('Selects a persisted scope', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); await userEvents.type(getTreeSearch(), 'slothVoteTracker'); await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3)); await userEvents.click(getResultApplicationsSlothVoteTrackerSelect()); - await userEvents.click(getFiltersApply()); - expect(getFiltersInput().value).toBe('slothPictureFactory, slothVoteTracker'); + await userEvents.click(getSelectorApply()); + expect(getSelectorInput().value).toBe('slothPictureFactory, slothVoteTracker'); }); it('Deselects a persisted scope', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); await userEvents.type(getTreeSearch(), 'slothVoteTracker'); await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3)); await userEvents.click(getResultApplicationsSlothVoteTrackerSelect()); - await userEvents.click(getFiltersApply()); - expect(getFiltersInput().value).toBe('slothPictureFactory, slothVoteTracker'); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorApply()); + expect(getSelectorInput().value).toBe('slothPictureFactory, slothVoteTracker'); + await userEvents.click(getSelectorInput()); await userEvents.click(getPersistedApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); - expect(getFiltersInput().value).toBe('slothVoteTracker'); + await userEvents.click(getSelectorApply()); + expect(getSelectorInput().value).toBe('slothVoteTracker'); }); it('Shows the proper headline', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); expect(getTreeHeadline()).toHaveTextContent('Recommended'); await userEvents.type(getTreeSearch(), 'Applications'); await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(2)); @@ -297,90 +298,90 @@ describe('ScopesScene', () => { }); }); - describe('Filters', () => { + describe('Selector', () => { it('Opens', async () => { - await userEvents.click(getFiltersInput()); - expect(getFiltersApply()).toBeInTheDocument(); + await userEvents.click(getSelectorInput()); + expect(getSelectorApply()).toBeInTheDocument(); }); it('Fetches scope details on save', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultClustersSelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); await waitFor(() => expect(fetchSelectedScopesSpy).toHaveBeenCalled()); - expect(filtersScene.getSelectedScopes()).toEqual( + expect(getClosestScopesFacade(dashboardScene)?.value).toEqual( mocksScopes.filter(({ metadata: { name } }) => name === 'indexHelperCluster') ); }); it("Doesn't save the scopes on close", async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultClustersSelect()); - await userEvents.click(getFiltersCancel()); + await userEvents.click(getSelectorCancel()); await waitFor(() => expect(fetchSelectedScopesSpy).not.toHaveBeenCalled()); - expect(filtersScene.getSelectedScopes()).toEqual([]); + expect(getClosestScopesFacade(dashboardScene)?.value).toEqual([]); }); it('Shows selected scopes', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultClustersSelect()); - await userEvents.click(getFiltersApply()); - expect(getFiltersInput().value).toEqual('Cluster Index Helper'); + await userEvents.click(getSelectorApply()); + expect(getSelectorInput().value).toEqual('Cluster Index Helper'); }); }); describe('Dashboards list', () => { it('Toggles expanded state', async () => { await userEvents.click(getDashboardsExpand()); - expect(getDashboardsContainer()).toBeInTheDocument(); + expect(getNotFoundNoScopes()).toBeInTheDocument(); }); it('Does not fetch dashboards list when the list is not expanded', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); await waitFor(() => expect(fetchSuggestedDashboardsSpy).not.toHaveBeenCalled()); }); it('Fetches dashboards list when the list is expanded', async () => { await userEvents.click(getDashboardsExpand()); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); await waitFor(() => expect(fetchSuggestedDashboardsSpy).toHaveBeenCalled()); }); it('Fetches dashboards list when the list is expanded after scope selection', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); await userEvents.click(getDashboardsExpand()); await waitFor(() => expect(fetchSuggestedDashboardsSpy).toHaveBeenCalled()); }); it('Shows dashboards for multiple scopes', async () => { await userEvents.click(getDashboardsExpand()); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); expect(getDashboard('1')).toBeInTheDocument(); expect(getDashboard('2')).toBeInTheDocument(); expect(queryDashboard('3')).not.toBeInTheDocument(); expect(queryDashboard('4')).not.toBeInTheDocument(); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsSlothVoteTrackerSelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); expect(getDashboard('1')).toBeInTheDocument(); expect(getDashboard('2')).toBeInTheDocument(); expect(getDashboard('3')).toBeInTheDocument(); expect(getDashboard('4')).toBeInTheDocument(); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); expect(queryDashboard('1')).not.toBeInTheDocument(); expect(queryDashboard('2')).not.toBeInTheDocument(); expect(getDashboard('3')).toBeInTheDocument(); @@ -389,10 +390,10 @@ describe('ScopesScene', () => { it('Filters the dashboards list', async () => { await userEvents.click(getDashboardsExpand()); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); expect(getDashboard('1')).toBeInTheDocument(); expect(getDashboard('2')).toBeInTheDocument(); await userEvents.type(getDashboardsSearch(), '1'); @@ -401,12 +402,12 @@ describe('ScopesScene', () => { it('Deduplicates the dashboards list', async () => { await userEvents.click(getDashboardsExpand()); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsClustersExpand()); await userEvents.click(getResultApplicationsClustersSlothClusterNorthSelect()); await userEvents.click(getResultApplicationsClustersSlothClusterSouthSelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); expect(queryAllDashboard('5')).toHaveLength(1); expect(queryAllDashboard('6')).toHaveLength(1); expect(queryAllDashboard('7')).toHaveLength(1); @@ -421,20 +422,20 @@ describe('ScopesScene', () => { it('Does not show the input when there are no dashboards found for scope', async () => { await userEvents.click(getDashboardsExpand()); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultClustersExpand()); await userEvents.click(getResultClustersSlothClusterEastRadio()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); expect(getNotFoundForScope()).toBeInTheDocument(); expect(queryDashboardsSearch()).not.toBeInTheDocument(); }); it('Does show the input and a message when there are no dashboards found for filter', async () => { await userEvents.click(getDashboardsExpand()); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); await userEvents.type(getDashboardsSearch(), 'unknown'); expect(queryDashboardsSearch()).toBeInTheDocument(); expect(getNotFoundForFilter()).toBeInTheDocument(); @@ -446,14 +447,14 @@ describe('ScopesScene', () => { describe('View mode', () => { it('Enters view mode', async () => { await act(async () => dashboardScene.onEnterEditMode()); - expect(scopesScene.state.isViewing).toEqual(true); - expect(scopesScene.state.isExpanded).toEqual(false); + expect(scopesSelectorScene?.state?.isReadOnly).toEqual(true); + expect(scopesDashboardsScene?.state?.isPanelOpened).toEqual(false); }); - it('Closes filters on enter', async () => { - await userEvents.click(getFiltersInput()); + it('Closes selector on enter', async () => { + await userEvents.click(getSelectorInput()); await act(async () => dashboardScene.onEnterEditMode()); - expect(queryFiltersApply()).not.toBeInTheDocument(); + expect(querySelectorApply()).not.toBeInTheDocument(); }); it('Closes dashboards list on enter', async () => { @@ -462,24 +463,24 @@ describe('ScopesScene', () => { expect(queryDashboardsContainer()).not.toBeInTheDocument(); }); - it('Does not open filters when view mode is active', async () => { + it('Does not open selector when view mode is active', async () => { await act(async () => dashboardScene.onEnterEditMode()); - await userEvents.click(getFiltersInput()); - expect(queryFiltersApply()).not.toBeInTheDocument(); + await userEvents.click(getSelectorInput()); + expect(querySelectorApply()).not.toBeInTheDocument(); }); - it('Hides the expand button when view mode is active', async () => { + it('Disables the expand button when view mode is active', async () => { await act(async () => dashboardScene.onEnterEditMode()); - expect(queryDashboardsExpand()).not.toBeInTheDocument(); + expect(getDashboardsExpand()).toBeDisabled(); }); }); describe('Enrichers', () => { it('Data requests', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); await waitFor(() => { const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!; expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual( @@ -487,9 +488,9 @@ describe('ScopesScene', () => { ); }); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsSlothVoteTrackerSelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); await waitFor(() => { const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!; expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual( @@ -499,9 +500,9 @@ describe('ScopesScene', () => { ); }); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); await waitFor(() => { const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!; expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual( @@ -511,19 +512,19 @@ describe('ScopesScene', () => { }); it('Filters requests', async () => { - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsExpand()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); await waitFor(() => { expect(dashboardScene.enrichFiltersRequest().scopes).toEqual( mocksScopes.filter(({ metadata: { name } }) => name === 'slothPictureFactory') ); }); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsSlothVoteTrackerSelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); await waitFor(() => { expect(dashboardScene.enrichFiltersRequest().scopes).toEqual( mocksScopes.filter( @@ -532,9 +533,9 @@ describe('ScopesScene', () => { ); }); - await userEvents.click(getFiltersInput()); + await userEvents.click(getSelectorInput()); await userEvents.click(getResultApplicationsSlothPictureFactorySelect()); - await userEvents.click(getFiltersApply()); + await userEvents.click(getSelectorApply()); await waitFor(() => { expect(dashboardScene.enrichFiltersRequest().scopes).toEqual( mocksScopes.filter(({ metadata: { name } }) => name === 'slothVoteTracker') @@ -556,17 +557,24 @@ describe('ScopesScene', () => { locationService.push('/?scopes=scope1&scopes=scope2&scopes=scope3'); }); - it('Legacy API should not pass the scopes', () => { + afterEach(() => { + resetScenes(); + cleanup(); + }); + + it('Legacy API should not pass the scopes', async () => { config.featureToggles.kubernetesDashboards = false; getDashboardAPI().getDashboardDTO('1'); - expect(getMock).toHaveBeenCalledWith('/api/dashboards/uid/1', undefined); + await waitFor(() => expect(getMock).toHaveBeenCalledWith('/api/dashboards/uid/1', undefined)); }); - it('K8s API should not pass the scopes', () => { + it('K8s API should not pass the scopes', async () => { config.featureToggles.kubernetesDashboards = true; getDashboardAPI().getDashboardDTO('1'); - expect(getMock).toHaveBeenCalledWith( - '/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/1/dto' + await waitFor(() => + expect(getMock).toHaveBeenCalledWith( + '/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/1/dto' + ) ); }); }); @@ -580,19 +588,29 @@ describe('ScopesScene', () => { beforeEach(() => { setDashboardAPI(undefined); locationService.push('/?scopes=scope1&scopes=scope2&scopes=scope3'); + initializeScopes(); + }); + + afterEach(() => { + resetScenes(); + cleanup(); }); - it('Legacy API should pass the scopes', () => { + it('Legacy API should pass the scopes', async () => { config.featureToggles.kubernetesDashboards = false; getDashboardAPI().getDashboardDTO('1'); - expect(getMock).toHaveBeenCalledWith('/api/dashboards/uid/1', { scopes: ['scope1', 'scope2', 'scope3'] }); + await waitFor(() => + expect(getMock).toHaveBeenCalledWith('/api/dashboards/uid/1', { scopes: ['scope1', 'scope2', 'scope3'] }) + ); }); - it('K8s API should not pass the scopes', () => { + it('K8s API should not pass the scopes', async () => { config.featureToggles.kubernetesDashboards = true; getDashboardAPI().getDashboardDTO('1'); - expect(getMock).toHaveBeenCalledWith( - '/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/1/dto' + await waitFor(() => + expect(getMock).toHaveBeenCalledWith( + '/apis/dashboard.grafana.app/v0alpha1/namespaces/default/dashboards/1/dto' + ) ); }); }); diff --git a/public/app/features/dashboard-scene/scene/Scopes/testUtils.tsx b/public/app/features/scopes/testUtils.tsx similarity index 88% rename from public/app/features/dashboard-scene/scene/Scopes/testUtils.tsx rename to public/app/features/scopes/testUtils.tsx index f83bf074dfb..1ab33d8e7ef 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/testUtils.tsx +++ b/public/app/features/scopes/testUtils.tsx @@ -1,11 +1,13 @@ import { screen } from '@testing-library/react'; -import { render } from 'test/test-utils'; +import { KBarProvider } from 'kbar'; +import { getWrapper, render } from 'test/test-utils'; import { Scope, ScopeDashboardBinding, ScopeNode } from '@grafana/data'; import { AdHocFiltersVariable, behaviors, GroupByVariable, + sceneGraph, SceneGridItem, SceneGridLayout, SceneQueryRunner, @@ -13,10 +15,18 @@ import { SceneVariableSet, VizPanel, } from '@grafana/scenes'; +import { AppChrome } from 'app/core/components/AppChrome/AppChrome'; +import { AppChromeService } from 'app/core/components/AppChrome/AppChromeService'; import { DashboardControls } from 'app/features/dashboard-scene/scene//DashboardControls'; import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; +import { configureStore } from 'app/store/configureStore'; -import * as api from './api'; +import { ScopesFacade } from './ScopesFacadeScene'; +import { scopesDashboardsScene, scopesSelectorScene } from './instance'; +import { getInitialDashboardsState } from './internal/ScopesDashboardsScene'; +import { initialSelectorState } from './internal/ScopesSelectorScene'; +import * as api from './internal/api'; +import { DASHBOARDS_OPENED_KEY } from './internal/const'; export const mocksScopes: Scope[] = [ { @@ -321,12 +331,12 @@ const selectors = { expand: (nodeId: string, type: 'result' | 'persisted') => `scopes-tree-${type}-${nodeId}-expand`, title: (nodeId: string, type: 'result' | 'persisted') => `scopes-tree-${type}-${nodeId}-title`, }, - filters: { - input: 'scopes-filters-input', - container: 'scopes-filters-container', - loading: 'scopes-filters-loading', - apply: 'scopes-filters-apply', - cancel: 'scopes-filters-cancel', + selector: { + input: 'scopes-selector-input', + container: 'scopes-selector-container', + loading: 'scopes-selector-loading', + apply: 'scopes-selector-apply', + cancel: 'scopes-selector-cancel', }, dashboards: { expand: 'scopes-dashboards-expand', @@ -341,15 +351,13 @@ const selectors = { }, }; -export const getFiltersInput = () => screen.getByTestId(selectors.filters.input); -export const queryFiltersApply = () => screen.queryByTestId(selectors.filters.apply); -export const getFiltersApply = () => screen.getByTestId(selectors.filters.apply); -export const getFiltersCancel = () => screen.getByTestId(selectors.filters.cancel); +export const getSelectorInput = () => screen.getByTestId(selectors.selector.input); +export const querySelectorApply = () => screen.queryByTestId(selectors.selector.apply); +export const getSelectorApply = () => screen.getByTestId(selectors.selector.apply); +export const getSelectorCancel = () => screen.getByTestId(selectors.selector.cancel); -export const queryDashboardsExpand = () => screen.queryByTestId(selectors.dashboards.expand); export const getDashboardsExpand = () => screen.getByTestId(selectors.dashboards.expand); export const queryDashboardsContainer = () => screen.queryByTestId(selectors.dashboards.container); -export const getDashboardsContainer = () => screen.getByTestId(selectors.dashboards.container); export const queryDashboardsSearch = () => screen.queryByTestId(selectors.dashboards.search); export const getDashboardsSearch = () => screen.getByTestId(selectors.dashboards.search); export const queryAllDashboard = (uid: string) => screen.queryAllByTestId(selectors.dashboards.dashboard(uid)); @@ -416,7 +424,12 @@ export function buildTestScene(overrides: Partial = {}) { timeZone: 'browser', }), controls: new DashboardControls({}), - $behaviors: [new behaviors.CursorSync({})], + $behaviors: [ + new behaviors.CursorSync({}), + new ScopesFacade({ + handler: (facade) => sceneGraph.getTimeRange(facade).onRefresh(), + }), + ], $variables: new SceneVariableSet({ variables: [ new AdHocFiltersVariable({ @@ -451,5 +464,31 @@ export function buildTestScene(overrides: Partial = {}) { } export function renderDashboard(dashboardScene: DashboardScene) { - return render(); + const store = configureStore(); + const chrome = new AppChromeService(); + chrome.update({ chromeless: false }); + const Wrapper = getWrapper({ store, renderWithRouter: true, grafanaContext: { chrome } }); + + return render( + + + + + + + , + { + historyOptions: { + initialEntries: ['/'], + }, + } + ); +} + +export function resetScenes() { + scopesSelectorScene?.setState(initialSelectorState); + + localStorage.removeItem(DASHBOARDS_OPENED_KEY); + + scopesDashboardsScene?.setState(getInitialDashboardsState()); } diff --git a/public/app/features/scopes/utils.ts b/public/app/features/scopes/utils.ts new file mode 100644 index 00000000000..85d01626fa8 --- /dev/null +++ b/public/app/features/scopes/utils.ts @@ -0,0 +1,39 @@ +import { Scope } from '@grafana/data'; +import { sceneGraph, SceneObject } from '@grafana/scenes'; + +import { ScopesFacade } from './ScopesFacadeScene'; +import { scopesDashboardsScene, scopesSelectorScene } from './instance'; +import { getScopesFromSelectedScopes } from './internal/utils'; + +export function getSelectedScopes(): Scope[] { + return getScopesFromSelectedScopes(scopesSelectorScene?.state.scopes ?? []); +} + +export function getSelectedScopesNames(): string[] { + return getSelectedScopes().map((scope) => scope.metadata.name); +} + +export function enableScopes() { + scopesSelectorScene?.enable(); + scopesDashboardsScene?.enable(); +} + +export function disableScopes() { + scopesSelectorScene?.disable(); + scopesDashboardsScene?.disable(); +} + +export function exitScopesReadOnly() { + scopesSelectorScene?.exitReadOnly(); + scopesDashboardsScene?.enable(); +} + +export function enterScopesReadOnly() { + scopesSelectorScene?.enterReadOnly(); + scopesDashboardsScene?.disable(); +} + +export function getClosestScopesFacade(scene: SceneObject): ScopesFacade | null { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + return sceneGraph.findObject(scene, (obj) => obj instanceof ScopesFacade) as ScopesFacade | null; +} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 84a34e98efb..b2112d30d29 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1911,16 +1911,7 @@ "dismissable-button": "Close" }, "scopes": { - "filters": { - "apply": "Apply", - "cancel": "Cancel", - "input": { - "placeholder": "Select scopes...", - "removeAll": "Remove all scopes" - }, - "title": "Select scopes" - }, - "suggestedDashboards": { + "dashboards": { "loading": "Loading dashboards", "noResultsForFilter": "No results found for your query", "noResultsForFilterClear": "Clear search", @@ -1928,10 +1919,20 @@ "noResultsNoScopes": "No scopes selected", "search": "Search", "toggle": { - "collapse": "Collapse scope filters", - "expand": "Expand scope filters" + "collapse": "Collapse suggested dashboards list", + "disabled": "Suggested dashboards list is disabled due to read only mode", + "expand": "Expand suggested dashboards list" } }, + "selector": { + "apply": "Apply", + "cancel": "Cancel", + "input": { + "placeholder": "Select scopes...", + "removeAll": "Remove all scopes" + }, + "title": "Select scopes" + }, "tree": { "collapse": "Collapse", "expand": "Expand", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 067128ad03d..244caed14ca 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -1911,16 +1911,7 @@ "dismissable-button": "Cľőşę" }, "scopes": { - "filters": { - "apply": "Åppľy", - "cancel": "Cäʼnčęľ", - "input": { - "placeholder": "Ŝęľęčŧ şčőpęş...", - "removeAll": "Ŗęmővę äľľ şčőpęş" - }, - "title": "Ŝęľęčŧ şčőpęş" - }, - "suggestedDashboards": { + "dashboards": { "loading": "Ŀőäđįʼnģ đäşĥþőäřđş", "noResultsForFilter": "Ńő řęşūľŧş ƒőūʼnđ ƒőř yőūř qūęřy", "noResultsForFilterClear": "Cľęäř şęäřčĥ", @@ -1928,10 +1919,20 @@ "noResultsNoScopes": "Ńő şčőpęş şęľęčŧęđ", "search": "Ŝęäřčĥ", "toggle": { - "collapse": "Cőľľäpşę şčőpę ƒįľŧęřş", - "expand": "Ēχpäʼnđ şčőpę ƒįľŧęřş" + "collapse": "Cőľľäpşę şūģģęşŧęđ đäşĥþőäřđş ľįşŧ", + "disabled": "Ŝūģģęşŧęđ đäşĥþőäřđş ľįşŧ įş đįşäþľęđ đūę ŧő řęäđ őʼnľy mőđę", + "expand": "Ēχpäʼnđ şūģģęşŧęđ đäşĥþőäřđş ľįşŧ" } }, + "selector": { + "apply": "Åppľy", + "cancel": "Cäʼnčęľ", + "input": { + "placeholder": "Ŝęľęčŧ şčőpęş...", + "removeAll": "Ŗęmővę äľľ şčőpęş" + }, + "title": "Ŝęľęčŧ şčőpęş" + }, "tree": { "collapse": "Cőľľäpşę", "expand": "Ēχpäʼnđ", diff --git a/public/test/test-utils.tsx b/public/test/test-utils.tsx index dae68331554..1e6e5cee3b0 100644 --- a/public/test/test-utils.tsx +++ b/public/test/test-utils.tsx @@ -46,7 +46,7 @@ const getWrapper = ({ historyOptions, grafanaContext, }: ExtendedRenderOptions & { - grafanaContext?: GrafanaContextType; + grafanaContext?: Partial; }) => { const reduxStore = store || configureStore(); /** From f11d6ebcd0fefd70e7021c78da0606e5b8914402 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Mon, 12 Aug 2024 13:37:17 +0200 Subject: [PATCH 015/229] Alerting: Update contact points list and mute timings list on update (#91776) make sure that contact points list and mute timings list are updated when we update the alertmanager configuration --- public/app/features/alerting/unified/api/alertmanagerApi.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/app/features/alerting/unified/api/alertmanagerApi.ts b/public/app/features/alerting/unified/api/alertmanagerApi.ts index 0ab462658e2..a979d952108 100644 --- a/public/app/features/alerting/unified/api/alertmanagerApi.ts +++ b/public/app/features/alerting/unified/api/alertmanagerApi.ts @@ -279,9 +279,11 @@ export const alertmanagerApi = alertingApi.injectEndpoints({ // TODO: Remove as part of migration to k8s API for receivers getContactPointsList: build.query({ query: () => ({ url: '/api/v1/notifications/receivers' }), + providesTags: ['AlertmanagerConfiguration'], }), getMuteTimingList: build.query({ query: () => ({ url: '/api/v1/notifications/time-intervals' }), + providesTags: ['AlertmanagerConfiguration'], }), }), }); From 5487ea444a11f926b1086bee16863ce50687cf74 Mon Sep 17 00:00:00 2001 From: Santiago Date: Mon, 12 Aug 2024 13:45:22 +0200 Subject: [PATCH 016/229] Alerting: Update remote Alertmanager config marshalling in test (#91791) --- pkg/services/ngalert/remote/alertmanager_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/services/ngalert/remote/alertmanager_test.go b/pkg/services/ngalert/remote/alertmanager_test.go index e3d923f0ea3..b04c7194ee0 100644 --- a/pkg/services/ngalert/remote/alertmanager_test.go +++ b/pkg/services/ngalert/remote/alertmanager_test.go @@ -767,7 +767,6 @@ global: http_config: follow_redirects: true enable_http2: true - http_headers: null smtp_hello: localhost smtp_require_tls: true pagerduty_url: https://events.pagerduty.com/v2/enqueue From add99fb3d0adbf87fa71f93d19e90b415a61a603 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Mon, 12 Aug 2024 13:51:12 +0200 Subject: [PATCH 017/229] Alerting: Add basic support for active_time_intervals (#91710) --- .betterer.results | 19 ----- .../unified/NotificationPolicies.test.tsx | 2 +- .../mute-timings/useMuteTimings.tsx | 4 +- .../notification-policies/Policy.test.tsx | 6 +- .../notification-policies/Policy.tsx | 77 ++++++++++++++----- .../alerting/unified/state/actions.ts | 4 +- .../unified/utils/alertmanager.test.ts | 10 ++- .../alerting/unified/utils/alertmanager.ts | 14 ++-- .../plugins/datasource/alertmanager/types.ts | 3 + public/locales/en-US/grafana.json | 24 +++++- public/locales/pseudo-LOCALE/grafana.json | 24 +++++- 11 files changed, 130 insertions(+), 57 deletions(-) diff --git a/.betterer.results b/.betterer.results index c0ff4acd23b..0f1715e5605 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1803,25 +1803,6 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "12"], [0, 0, 0, "No untranslated strings. Wrap text with ", "13"] ], - "public/app/features/alerting/unified/components/notification-policies/Policy.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "8"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "9"], - [0, 0, 0, "Unexpected any. Specify a different type.", "10"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "11"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "12"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "13"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "14"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "15"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "16"] - ], "public/app/features/alerting/unified/components/notification-policies/PromDurationDocs.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], diff --git a/public/app/features/alerting/unified/NotificationPolicies.test.tsx b/public/app/features/alerting/unified/NotificationPolicies.test.tsx index 919a19d2d78..466e034cd4c 100644 --- a/public/app/features/alerting/unified/NotificationPolicies.test.tsx +++ b/public/app/features/alerting/unified/NotificationPolicies.test.tsx @@ -644,7 +644,7 @@ describe('NotificationPolicies', () => { renderNotificationPolicies(dataSources.promAlertManager.name); const rootRouteContainer = await ui.rootRouteContainer.find(); await waitFor(() => - expect(within(rootRouteContainer).getByTestId('matching-instances')).toHaveTextContent('0instances') + expect(within(rootRouteContainer).getByTestId('matching-instances')).toHaveTextContent('0instance') ); expect(ui.editButton.query(rootRouteContainer)).not.toBeInTheDocument(); diff --git a/public/app/features/alerting/unified/components/mute-timings/useMuteTimings.tsx b/public/app/features/alerting/unified/components/mute-timings/useMuteTimings.tsx index bbd6e3acca0..6d13a6037a8 100644 --- a/public/app/features/alerting/unified/components/mute-timings/useMuteTimings.tsx +++ b/public/app/features/alerting/unified/components/mute-timings/useMuteTimings.tsx @@ -10,7 +10,7 @@ import { } from 'app/features/alerting/unified/openapi/timeIntervalsApi.gen'; import { deleteMuteTimingAction, updateAlertManagerConfigAction } from 'app/features/alerting/unified/state/actions'; import { BaseAlertmanagerArgs } from 'app/features/alerting/unified/types/hooks'; -import { renameMuteTimings } from 'app/features/alerting/unified/utils/alertmanager'; +import { renameTimeInterval } from 'app/features/alerting/unified/utils/alertmanager'; import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; import { PROVENANCE_ANNOTATION, PROVENANCE_NONE } from 'app/features/alerting/unified/utils/k8s/constants'; import { getK8sNamespace, shouldUseK8sApi } from 'app/features/alerting/unified/utils/k8s/utils'; @@ -261,7 +261,7 @@ export const useUpdateMuteTiming = ({ alertmanager }: BaseAlertmanagerArgs) => { } if (nameHasChanged && draft.alertmanager_config.route) { - draft.alertmanager_config.route = renameMuteTimings( + draft.alertmanager_config.route = renameTimeInterval( timeInterval.name, originalName, draft.alertmanager_config.route diff --git a/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx b/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx index 739b10fac0e..77d54ae2a5c 100644 --- a/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx @@ -100,8 +100,9 @@ describe('Policy', () => { // for grouping expect(within(defaultPolicy).getByTestId('grouping')).toHaveTextContent('grafana_folder, alertname'); - // no mute timings + // no timings expect(within(defaultPolicy).queryByTestId('mute-timings')).not.toBeInTheDocument(); + expect(within(defaultPolicy).queryByTestId('active-timings')).not.toBeInTheDocument(); // for timing options expect(within(defaultPolicy).getByTestId('timing-options')).toHaveTextContent( @@ -132,6 +133,7 @@ describe('Policy', () => { // expect(within(firstPolicy).getByTestId('matching-instances')).toHaveTextContent('0instances'); expect(within(firstPolicy).getByTestId('contact-point')).toHaveTextContent('provisioned-contact-point'); expect(within(firstPolicy).getByTestId('mute-timings')).toHaveTextContent('Muted whenmt-1'); + expect(within(firstPolicy).getByTestId('active-timings')).toHaveTextContent('Active whenmt-2'); expect(within(firstPolicy).getByTestId('inherited-properties')).toHaveTextContent('Inherited2 properties'); // second custom policy should be correct @@ -139,6 +141,7 @@ describe('Policy', () => { expect(within(secondPolicy).getByTestId('label-matchers')).toHaveTextContent(/^region \= EMEA$/); expect(within(secondPolicy).queryByTestId('continue-matching')).not.toBeInTheDocument(); expect(within(secondPolicy).queryByTestId('mute-timings')).not.toBeInTheDocument(); + expect(within(secondPolicy).queryByTestId('active-timings')).not.toBeInTheDocument(); expect(within(secondPolicy).getByTestId('inherited-properties')).toHaveTextContent('Inherited3 properties'); // third custom policy should be correct @@ -360,6 +363,7 @@ const mockRoutes: RouteWithID = { receiver: 'provisioned-contact-point', object_matchers: [['team', eq, 'operations']], mute_time_intervals: ['mt-1'], + active_time_intervals: ['mt-2'], continue: true, routes: [ { diff --git a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx index b4239207f8f..0560d6bb20b 100644 --- a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx @@ -163,6 +163,7 @@ const Policy = (props: PolicyComponentProps) => { const groupBy = currentRoute.group_by; const muteTimings = currentRoute.mute_time_intervals ?? []; + const activeTimings = currentRoute.active_time_intervals ?? []; const timingOptions: TimingOptions = { group_wait: currentRoute.group_wait, @@ -245,7 +246,9 @@ const Policy = (props: PolicyComponentProps) => { ) : hasMatchers ? ( ) : ( - No matchers + + No matchers + )} {/* TODO maybe we should move errors to the gutter instead? */} @@ -264,7 +267,7 @@ const Policy = (props: PolicyComponentProps) => { type="button" onClick={() => onAddPolicy(currentRoute, 'child')} > - New child policy + New child policy ) : ( { icon="angle-down" type="button" > - Add new policy + Add new policy )} @@ -326,6 +329,7 @@ const Policy = (props: PolicyComponentProps) => { contactPoint={contactPoint ?? undefined} groupBy={groupBy} muteTimings={muteTimings} + activeTimings={activeTimings} timingOptions={timingOptions} inheritedProperties={inheritedProperties} alertManagerSourceName={alertManagerSourceName} @@ -379,7 +383,9 @@ const Policy = (props: PolicyComponentProps) => { className={styles.moreButtons} onClick={() => setVisibleChildPolicies(visibleChildPolicies + POLICIES_PER_PAGE)} > - {moreCount} additional {pluralize('policy', moreCount)} + + {{ count: moreCount }} additional policies + )} @@ -397,6 +403,7 @@ interface MetadataRowProps { contactPoint?: string; groupBy?: string[]; muteTimings?: string[]; + activeTimings?: string[]; timingOptions?: TimingOptions; inheritedProperties?: Partial; alertManagerSourceName: string; @@ -417,6 +424,7 @@ function MetadataRow({ timingOptions, groupBy, muteTimings = [], + activeTimings = [], matchingInstancesPreview, inheritedProperties, matchingAlertGroups, @@ -436,6 +444,7 @@ function MetadataRow({ const singleGroup = isDefaultPolicy && isArray(groupBy) && groupBy.length === 0; const hasMuteTimings = Boolean(muteTimings.length); + const hasActiveTimings = Boolean(activeTimings.length); return (
@@ -450,12 +459,18 @@ function MetadataRow({ data-testid="matching-instances" > {numberOfAlertInstances ?? '-'} - {pluralize('instance', numberOfAlertInstances)} + + + instance + + )} {contactPoint && ( - Delivered to + + Delivered to + {customGrouping && ( - Grouped by + + Grouped by + {groupBy.join(', ')} )} {singleGroup && ( - Single group + + Single group + )} {noGrouping && ( - Not grouping + + Not grouping + )} )} {hasMuteTimings && ( - Muted when - + + Muted when + + + + )} + {hasActiveTimings && ( + + + Active when + + )} {timingOptions && ( @@ -498,7 +529,9 @@ function MetadataRow({ {hasInheritedProperties && ( <> - Inherited + + Inherited + @@ -514,7 +547,7 @@ export const useCreateDropdownMenuActions = ( provisioned: boolean, onEditPolicy: (route: RouteWithID, isDefault?: boolean, readOnly?: boolean) => void, currentRoute: RouteWithID, - toggleShowExportDrawer: (nextValue?: any) => void, + toggleShowExportDrawer: () => void, onDeletePolicy: (route: RouteWithID) => void ) => { const [ @@ -644,10 +677,12 @@ function DefaultPolicyIndicator() { return ( <> - Default policy + Default policy - All alert instances will be handled by the default policy if no other matching policies are found. + + All alert instances will be handled by the default policy if no other matching policies are found. + ); @@ -656,7 +691,7 @@ function DefaultPolicyIndicator() { function AutogeneratedRootIndicator() { return ( - Auto-generated policies + Auto-generated policies ); } @@ -683,7 +718,7 @@ const InheritedProperties: FC<{ properties: InheritableProperties }> = ({ proper ); -const MuteTimings: FC<{ timings: string[]; alertManagerSourceName: string }> = ({ +const TimeIntervals: FC<{ timings: string[]; alertManagerSourceName: string }> = ({ timings, alertManagerSourceName, }) => { @@ -852,7 +887,9 @@ const ContactPointsHoverDetails: FC = ({ placement="top" header={ -
Contact Point
+
+ Contact Point +
{contactPoint}
} @@ -930,7 +967,7 @@ const routePropertyToValue = ( if (isNotGrouping) { return ( - Not grouping + Not grouping ); } @@ -938,7 +975,7 @@ const routePropertyToValue = ( if (isSingleGroup) { return ( - Single group + Single group ); } diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index a1c3ad5afa6..018a3ff68ff 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -52,7 +52,7 @@ import { discoverFeatures } from '../api/buildInfo'; import { FetchPromRulesFilter, fetchRules } from '../api/prometheus'; import { FetchRulerRulesFilter, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from '../api/ruler'; import { RuleFormValues } from '../types/rule-form'; -import { addDefaultsToAlertmanagerConfig, removeMuteTimingFromRoute } from '../utils/alertmanager'; +import { addDefaultsToAlertmanagerConfig, removeTimeIntervalFromRoute } from '../utils/alertmanager'; import { GRAFANA_RULES_SOURCE_NAME, getAllRulesSourceNames, @@ -590,7 +590,7 @@ export const deleteMuteTimingAction = (alertManagerSourceName: string, muteTimin alertmanager_config: { ...configWithoutMuteTimings, route: config.alertmanager_config.route - ? removeMuteTimingFromRoute(muteTimingName, config.alertmanager_config?.route) + ? removeTimeIntervalFromRoute(muteTimingName, config.alertmanager_config?.route) : undefined, ...time_intervals_without_mute_to_save, }, diff --git a/public/app/features/alerting/unified/utils/alertmanager.test.ts b/public/app/features/alerting/unified/utils/alertmanager.test.ts index 3c0293aef94..1607a72ac6b 100644 --- a/public/app/features/alerting/unified/utils/alertmanager.test.ts +++ b/public/app/features/alerting/unified/utils/alertmanager.test.ts @@ -1,7 +1,7 @@ import { Matcher, MatcherOperator, Route } from 'app/plugins/datasource/alertmanager/types'; import { Labels } from 'app/types/unified-alerting-dto'; -import { labelsMatchMatchers, removeMuteTimingFromRoute, matchersToString } from './alertmanager'; +import { labelsMatchMatchers, removeTimeIntervalFromRoute, matchersToString } from './alertmanager'; import { parseMatcher, parsePromQLStyleMatcherLooseSafe } from './matchers'; describe('Alertmanager utils', () => { @@ -104,6 +104,7 @@ describe('Alertmanager utils', () => { receiver: 'slack', object_matchers: [['env', MatcherOperator.equal, 'prod']], mute_time_intervals: ['test2'], + active_time_intervals: ['test1'], }, { receiver: 'pagerduty', @@ -113,13 +114,15 @@ describe('Alertmanager utils', () => { ], }; - it('should remove mute timings from routes', () => { - expect(removeMuteTimingFromRoute('test1', route)).toEqual({ + it('should remove time interval from routes', () => { + expect(removeTimeIntervalFromRoute('test1', route)).toEqual({ mute_time_intervals: ['test2'], + active_time_intervals: [], object_matchers: [['foo', '=', 'bar']], receiver: 'gmail', routes: [ { + active_time_intervals: [], mute_time_intervals: ['test2'], object_matchers: [['env', '=', 'prod']], receiver: 'slack', @@ -127,6 +130,7 @@ describe('Alertmanager utils', () => { }, { mute_time_intervals: [], + active_time_intervals: [], object_matchers: [['env', '=', 'eu']], receiver: 'pagerduty', routes: undefined, diff --git a/public/app/features/alerting/unified/utils/alertmanager.ts b/public/app/features/alerting/unified/utils/alertmanager.ts index 8a73dd2685c..c7e05d82dd0 100644 --- a/public/app/features/alerting/unified/utils/alertmanager.ts +++ b/public/app/features/alerting/unified/utils/alertmanager.ts @@ -35,22 +35,22 @@ export function addDefaultsToAlertmanagerConfig(config: AlertManagerCortexConfig return config; } -export function removeMuteTimingFromRoute(muteTiming: string, route: Route): Route { +export function removeTimeIntervalFromRoute(muteTiming: string, route: Route): Route { const newRoute: Route = { ...route, mute_time_intervals: route.mute_time_intervals?.filter((muteName) => muteName !== muteTiming) ?? [], - routes: route.routes?.map((subRoute) => removeMuteTimingFromRoute(muteTiming, subRoute)), + active_time_intervals: route.active_time_intervals?.filter((muteName) => muteName !== muteTiming) ?? [], + routes: route.routes?.map((subRoute) => removeTimeIntervalFromRoute(muteTiming, subRoute)), }; return newRoute; } -export function renameMuteTimings(newMuteTimingName: string, oldMuteTimingName: string, route: Route): Route { +export function renameTimeInterval(newName: string, oldName: string, route: Route): Route { return { ...route, - mute_time_intervals: route.mute_time_intervals?.map((name) => - name === oldMuteTimingName ? newMuteTimingName : name - ), - routes: route.routes?.map((subRoute) => renameMuteTimings(newMuteTimingName, oldMuteTimingName, subRoute)), + mute_time_intervals: route.mute_time_intervals?.map((name) => (name === oldName ? newName : name)), + active_time_intervals: route.active_time_intervals?.map((name) => (name === oldName ? newName : name)), + routes: route.routes?.map((subRoute) => renameTimeInterval(newName, oldName, subRoute)), }; } diff --git a/public/app/plugins/datasource/alertmanager/types.ts b/public/app/plugins/datasource/alertmanager/types.ts index c5bcacd9457..c27d4e53122 100644 --- a/public/app/plugins/datasource/alertmanager/types.ts +++ b/public/app/plugins/datasource/alertmanager/types.ts @@ -119,7 +119,10 @@ export type Route = { group_interval?: string; repeat_interval?: string; routes?: Route[]; + /** Times when the route should be muted. */ mute_time_intervals?: string[]; + /** Times when the route should be active. This is the opposite of `mute_time_intervals` */ + active_time_intervals?: string[]; /** only the root policy might have a provenance field defined */ provenance?: string; }; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index b2112d30d29..f5a96871f45 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -106,6 +106,7 @@ "export-all": "Export all", "view": "View" }, + "contact-point": "Contact Point", "contact-points": { "delivery-duration": "Last delivery took <1>", "last-delivery-attempt": "Last delivery attempt", @@ -136,7 +137,23 @@ "saving": "Saving mute timing" }, "policies": { + "default-policy": { + "description": "All alert instances will be handled by the default policy if no other matching policies are found.", + "title": "Default policy" + }, + "generated-policies": "Auto-generated policies", "metadata": { + "active-time": "Active when", + "delivered-to": "Delivered to", + "grouped-by": "Grouped by", + "grouping": { + "none": "Not grouping", + "single-group": "Single group" + }, + "inherited": "Inherited", + "mute-time": "Muted when", + "n-instances_one": "instance", + "n-instances_other": "instances", "timingOptions": { "groupInterval": { "description": "How long to wait before sending a notification about new alerts that are added to a group of alerts for which an initial notification has already been sent.", @@ -151,7 +168,12 @@ "label": "Repeated every <1>" } } - } + }, + "n-more-policies_one": "{{count}} additional policy", + "n-more-policies_other": "{{count}} additional policies", + "new-child": "New child policy", + "new-policy": "Add new policy", + "no-matchers": "No matchers" }, "provisioning": { "badge-tooltip-provenance": "This resource has been provisioned via {{provenance}} and cannot be edited through the UI", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 244caed14ca..729d80a2982 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -106,6 +106,7 @@ "export-all": "Ēχpőřŧ äľľ", "view": "Vįęŵ" }, + "contact-point": "Cőʼnŧäčŧ Pőįʼnŧ", "contact-points": { "delivery-duration": "Ŀäşŧ đęľįvęřy ŧőőĸ <1>", "last-delivery-attempt": "Ŀäşŧ đęľįvęřy äŧŧęmpŧ", @@ -136,7 +137,23 @@ "saving": "Ŝävįʼnģ mūŧę ŧįmįʼnģ" }, "policies": { + "default-policy": { + "description": "Åľľ äľęřŧ įʼnşŧäʼnčęş ŵįľľ þę ĥäʼnđľęđ þy ŧĥę đęƒäūľŧ pőľįčy įƒ ʼnő őŧĥęř mäŧčĥįʼnģ pőľįčįęş äřę ƒőūʼnđ.", + "title": "Đęƒäūľŧ pőľįčy" + }, + "generated-policies": "Åūŧő-ģęʼnęřäŧęđ pőľįčįęş", "metadata": { + "active-time": "Åčŧįvę ŵĥęʼn", + "delivered-to": "Đęľįvęřęđ ŧő", + "grouped-by": "Ğřőūpęđ þy", + "grouping": { + "none": "Ńőŧ ģřőūpįʼnģ", + "single-group": "Ŝįʼnģľę ģřőūp" + }, + "inherited": "Ĩʼnĥęřįŧęđ", + "mute-time": "Mūŧęđ ŵĥęʼn", + "n-instances_one": "įʼnşŧäʼnčę", + "n-instances_other": "įʼnşŧäʼnčęş", "timingOptions": { "groupInterval": { "description": "Ħőŵ ľőʼnģ ŧő ŵäįŧ þęƒőřę şęʼnđįʼnģ ä ʼnőŧįƒįčäŧįőʼn äþőūŧ ʼnęŵ äľęřŧş ŧĥäŧ äřę äđđęđ ŧő ä ģřőūp őƒ äľęřŧş ƒőř ŵĥįčĥ äʼn įʼnįŧįäľ ʼnőŧįƒįčäŧįőʼn ĥäş äľřęäđy þęęʼn şęʼnŧ.", @@ -151,7 +168,12 @@ "label": "Ŗępęäŧęđ ęvęřy <1>" } } - } + }, + "n-more-policies_one": "{{count}} äđđįŧįőʼnäľ pőľįčy", + "n-more-policies_other": "{{count}} äđđįŧįőʼnäľ pőľįčįęş", + "new-child": "Ńęŵ čĥįľđ pőľįčy", + "new-policy": "Åđđ ʼnęŵ pőľįčy", + "no-matchers": "Ńő mäŧčĥęřş" }, "provisioning": { "badge-tooltip-provenance": "Ŧĥįş řęşőūřčę ĥäş þęęʼn přővįşįőʼnęđ vįä {{provenance}} äʼnđ čäʼnʼnőŧ þę ęđįŧęđ ŧĥřőūģĥ ŧĥę ŮĨ", From 78f78753c70cd0e10bae4286dc62fd582ce7755e Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Mon, 12 Aug 2024 12:55:22 +0100 Subject: [PATCH 018/229] Chore: Migrate `Modal` SCSS styles to emotion (#91569) migrate modal scss styles to emotion --- .../src/components/ConfirmModal.tsx | 8 +- .../src/components/Modal/ModalTabContent.tsx | 22 ++- .../src/themes/GlobalStyles/elements.ts | 7 + .../sharing/ShareExportTab.tsx | 2 +- .../dashboard-scene/sharing/ShareLinkTab.tsx | 2 +- .../sharing/ShareSnapshotTab.tsx | 4 +- .../components/ShareModal/ShareEmbed.tsx | 2 +- .../components/ShareModal/ShareExport.tsx | 2 +- .../ShareModal/ShareLibraryPanel.tsx | 2 +- .../components/ShareModal/ShareLink.tsx | 2 +- .../components/ShareModal/ShareSnapshot.tsx | 18 +- public/sass/_angular.scss | 79 ++++++++ public/sass/_grafana.scss | 1 - public/sass/components/_modals.scss | 169 ------------------ 14 files changed, 128 insertions(+), 192 deletions(-) delete mode 100644 public/sass/components/_modals.scss diff --git a/packages/grafana-sql/src/components/ConfirmModal.tsx b/packages/grafana-sql/src/components/ConfirmModal.tsx index 52090012b79..a926b5d74eb 100644 --- a/packages/grafana-sql/src/components/ConfirmModal.tsx +++ b/packages/grafana-sql/src/components/ConfirmModal.tsx @@ -25,7 +25,7 @@ export function ConfirmModal({ isOpen, onCancel, onDiscard, onCopy }: ConfirmMod return ( +
Warning
@@ -57,4 +57,10 @@ const getStyles = (theme: GrafanaTheme2) => ({ titleText: css({ paddingLeft: theme.spacing(2), }), + modalHeaderTitle: css({ + fontSize: theme.typography.size.lg, + float: 'left', + paddingTop: theme.spacing(1), + margin: theme.spacing(0, 2), + }), }); diff --git a/packages/grafana-ui/src/components/Modal/ModalTabContent.tsx b/packages/grafana-ui/src/components/Modal/ModalTabContent.tsx index 1efe5b489e4..85f5a979287 100644 --- a/packages/grafana-ui/src/components/Modal/ModalTabContent.tsx +++ b/packages/grafana-ui/src/components/Modal/ModalTabContent.tsx @@ -1,5 +1,9 @@ +import { css } from '@emotion/css'; import * as React from 'react'; +import { GrafanaTheme2 } from '@grafana/data'; + +import { useStyles2 } from '../../themes'; import { IconName } from '../../types'; interface Props { @@ -11,11 +15,23 @@ interface Props { /** @internal */ export const ModalTabContent = ({ children }: React.PropsWithChildren) => { + const styles = useStyles2(getStyles); + return ( -
-
-
{children}
+
+
+
{children}
); }; + +const getStyles = (theme: GrafanaTheme2) => ({ + header: css({ + display: 'flex', + margin: theme.spacing(0, 0, 3, 0), + }), + content: css({ + flexGrow: 1, + }), +}); diff --git a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts index 800a79b2b03..6586dbb6e0d 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts @@ -337,6 +337,13 @@ export function getElementStyles(theme: GrafanaTheme2) { '.template-variable': { color: theme.colors.primary.text, }, + + '.modal-header-title': { + fontSize: theme.typography.size.lg, + float: 'left', + paddingTop: theme.spacing(1), + margin: theme.spacing(0, 2), + }, }); } diff --git a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx index 746caf6b1ce..6ec5e4138bb 100644 --- a/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareExportTab.tsx @@ -111,7 +111,7 @@ function ShareExportTabRenderer({ model }: SceneComponentProps) <> {!isViewingJSON && ( <> -

+

Export this dashboard.

diff --git a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx index 65461f1d3bf..3a1ae740f1e 100644 --- a/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareLinkTab.tsx @@ -156,7 +156,7 @@ function ShareLinkTabRenderer({ model }: SceneComponentProps) { return ( <> -

+

Create a direct link to this dashboard or panel, customized with the options below. diff --git a/public/app/features/dashboard-scene/sharing/ShareSnapshotTab.tsx b/public/app/features/dashboard-scene/sharing/ShareSnapshotTab.tsx index f584a8cfe6a..1e25452f58b 100644 --- a/public/app/features/dashboard-scene/sharing/ShareSnapshotTab.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareSnapshotTab.tsx @@ -185,14 +185,14 @@ function ShareSnapshotTabRenderer({ model }: SceneComponentProps

-

+

A snapshot is an instant way to share an interactive dashboard publicly. When created, we strip sensitive data like queries (metric, template, and annotation) and panel links, leaving only the visible metric data and series names embedded in your dashboard.

-

+

Keep in mind, your snapshot can be viewed by anyone that has the link and can access the URL. Share wisely. diff --git a/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx b/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx index 5dd04a46885..64b416053a7 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareEmbed.tsx @@ -54,7 +54,7 @@ export function ShareEmbed({ panel, dashboard, range, buildIframe = buildIframeH return ( <> -

+

Generate HTML for embedding an iframe with this panel.

diff --git a/public/app/features/dashboard/components/ShareModal/ShareExport.tsx b/public/app/features/dashboard/components/ShareModal/ShareExport.tsx index aa942ce4877..46341ce195f 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareExport.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareExport.tsx @@ -101,7 +101,7 @@ export class ShareExport extends PureComponent { return ( <> -

+

Export this dashboard.

diff --git a/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx b/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx index 94aca5e30fc..1b4f57c6a0e 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareLibraryPanel.tsx @@ -22,7 +22,7 @@ export const ShareLibraryPanel = ({ panel, initialFolderUid, onCreateLibraryPane return ( <> -

+

Create library panel.

{ return ( <> -

+

Create a direct link to this dashboard or panel, customized with the options below. diff --git a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx index b6dd25275fb..d300289fe74 100644 --- a/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx +++ b/public/app/features/dashboard/components/ShareModal/ShareSnapshot.tsx @@ -240,14 +240,14 @@ export class ShareSnapshot extends PureComponent { return ( <>

-

+

A snapshot is an instant way to share an interactive dashboard publicly. When created, we strip sensitive data like queries (metric, template, and annotation) and panel links, leaving only the visible metric data and series names embedded in your dashboard.

-

+

Keep in mind, your snapshot can be viewed by anyone that has the link and can access the URL. Share wisely. @@ -317,14 +317,12 @@ export class ShareSnapshot extends PureComponent { renderStep3() { return ( -

-

- - The snapshot has been deleted. If you have already accessed it once, then it might take up to an hour before - before it is removed from browser caches or CDN caches. - -

-
+

+ + The snapshot has been deleted. If you have already accessed it once, then it might take up to an hour before + before it is removed from browser caches or CDN caches. + +

); } diff --git a/public/sass/_angular.scss b/public/sass/_angular.scss index 2b3a07c87cc..34ffeb57e2c 100644 --- a/public/sass/_angular.scss +++ b/public/sass/_angular.scss @@ -2171,3 +2171,82 @@ $easing: cubic-bezier(0, 0, 0.265, 1); div.flot-text { color: $text-color !important; } + +.modal-header { + background: $page-header-bg; + box-shadow: $page-header-shadow; + border-bottom: 1px solid $page-header-border-color; + display: flex; + align-items: center; + justify-content: space-between; +} + +.modal-header-close { + float: right; + padding: 9px $spacer; +} + +// Body (where all modal content resides) +.modal-body { + position: relative; +} + +.modal-content { + padding: $spacer * 2; + + &--has-scroll { + max-height: calc(100vh - 400px); + position: relative; + } +} + +// Footer (for actions) +.modal-footer { + padding: 14px 15px 15px; + border-top: 1px solid $panel-bg; + background-color: $panel-bg; + text-align: right; // right align buttons + // clear it in case folks use .pull-* classes on buttons + &::after { + content: ''; + display: table; + clear: both; + } +} + +.confirm-modal { + max-width: 500px; + + .confirm-modal-icon { + padding-top: 41px; + font-size: 280%; + color: $green-base; + padding-bottom: 20px; + } + + .confirm-modal-text { + font-size: $font-size-h4; + color: $link-color; + margin-bottom: $spacer * 2; + padding-top: $spacer; + } + + .confirm-modal-text2 { + font-size: $font-size-base; + padding-top: $spacer; + } + + .confirm-modal-buttons { + margin-bottom: $spacer; + button { + margin-right: calc($spacer/2); + } + } + + .modal-content-confirm-text { + margin-bottom: $space-xl; + span { + text-align: center; + } + } +} diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 272f946c31a..f46a1f671a7 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -14,7 +14,6 @@ // COMPONENTS @import 'components/buttons'; @import 'components/gf-form'; -@import 'components/modals'; @import 'components/dropdown'; @import 'components/dashboard_grid'; diff --git a/public/sass/components/_modals.scss b/public/sass/components/_modals.scss deleted file mode 100644 index 2c45bab98e1..00000000000 --- a/public/sass/components/_modals.scss +++ /dev/null @@ -1,169 +0,0 @@ -// -// Modals -// -------------------------------------------------- - -// Background -.modal-backdrop { - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: $zindex-modal-backdrop; - background-color: $modal-backdrop-bg; -} - -.modal-backdrop, -.modal-backdrop.fade.in { - opacity: 0.7; -} - -// Base modal -.modal { - position: fixed; - z-index: $zindex-modal; - width: 100%; - background: $page-bg; - box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - background-clip: padding-box; - outline: none; - - max-width: 750px; - left: 0; - right: 0; - margin-left: auto; - margin-right: auto; - top: 10%; -} - -.modal-header { - background: $page-header-bg; - box-shadow: $page-header-shadow; - border-bottom: 1px solid $page-header-border-color; - display: flex; - align-items: center; - justify-content: space-between; -} - -.modal-header-title { - font-size: $font-size-lg; - float: left; - padding-top: $space-sm; - margin: 0 $space-md; -} - -.modal-header-close { - float: right; - padding: 9px $spacer; -} - -// Body (where all modal content resides) -.modal-body { - position: relative; -} - -.modal-content { - padding: $spacer * 2; - - &--has-scroll { - max-height: calc(100vh - 400px); - position: relative; - } -} - -// Remove bottom margin if need be -.modal-form { - margin-bottom: 0; -} - -// Footer (for actions) -.modal-footer { - padding: 14px 15px 15px; - border-top: 1px solid $panel-bg; - background-color: $panel-bg; - text-align: right; // right align buttons - // clear it in case folks use .pull-* classes on buttons - &::after { - content: ''; - display: table; - clear: both; - } -} - -.modal--narrow { - max-width: 500px; -} - -.confirm-modal { - max-width: 500px; - - .confirm-modal-icon { - padding-top: 41px; - font-size: 280%; - color: $green-base; - padding-bottom: 20px; - } - - .confirm-modal-text { - font-size: $font-size-h4; - color: $link-color; - margin-bottom: $spacer * 2; - padding-top: $spacer; - } - - .confirm-modal-text2 { - font-size: $font-size-base; - padding-top: $spacer; - } - - .confirm-modal-buttons { - margin-bottom: $spacer; - button { - margin-right: calc($spacer/2); - } - } - - .modal-content-confirm-text { - margin-bottom: $space-xl; - span { - text-align: center; - } - } -} - -.share-modal-body { - .share-modal-options { - margin: 11px 0px 33px 0px; - display: inline-block; - } - - .share-modal-big-icon { - margin-right: 8px; - margin-top: -7px; - } - - .share-modal-info-text { - margin-top: 5px; - strong { - color: $text-color-emphasis; - font-weight: $font-weight-semi-bold; - } - } - - .share-modal-header { - display: flex; - margin: 0px 0 22px 0; - } - - .share-modal-content { - flex-grow: 1; - } - - .share-modal-link { - max-width: 716px; - white-space: nowrap; - overflow: hidden; - display: block; - text-overflow: ellipsis; - } -} From dface46474d98b31791a29ca29f5488dc647ccfc Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Mon, 12 Aug 2024 13:16:16 +0100 Subject: [PATCH 019/229] Chore: Migrate `gf-form` classes to emotion globals (#91632) * migrate gf-form classes to emotion globals * should be sm due to the different way the breakpoints work --- .betterer.results | 28 + .../src/themes/GlobalStyles/forms.ts | 340 ++++++++++++ public/sass/_angular.scss | 49 ++ public/sass/_grafana.scss | 1 - public/sass/components/_gf-form.scss | 484 ------------------ 5 files changed, 417 insertions(+), 485 deletions(-) delete mode 100644 public/sass/components/_gf-form.scss diff --git a/.betterer.results b/.betterer.results index 0f1715e5605..6e962654114 100644 --- a/.betterer.results +++ b/.betterer.results @@ -7574,6 +7574,34 @@ exports[`no gf-form usage`] = { [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] ], + "packages/grafana-ui/src/themes/GlobalStyles/forms.ts:5381": [ + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], + [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"] + ], "packages/grafana-ui/src/themes/GlobalStyles/legacySelect.ts:5381": [ [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], [0, 0, 0, "gf-form usage has been deprecated. Use a component from @grafana/ui or custom CSS instead.", "5381"], diff --git a/packages/grafana-ui/src/themes/GlobalStyles/forms.ts b/packages/grafana-ui/src/themes/GlobalStyles/forms.ts index c3a87d89adf..2cc1b03e94a 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/forms.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/forms.ts @@ -27,5 +27,345 @@ export function getFormElementStyles(theme: GrafanaTheme2) { { width: 'auto', // Override of generic input selector }, + '.gf-form': { + display: 'flex', + flexDirection: 'row', + alignItems: 'flex-start', + textAlign: 'left', + position: 'relative', + marginBottom: theme.spacing(0.5), + + '&--offset-1': { + marginLeft: theme.spacing(2), + }, + + '&--grow': { + flexGrow: 1, + }, + + '&--flex-end': { + justifyContent: 'flex-end', + }, + + '&--align-center': { + alignContent: 'center', + }, + + '&--alt': { + flexDirection: 'column', + alignItems: 'flex-start', + + '.gf-form-label': { + padding: '4px 0', + }, + }, + }, + '.gf-form--has-input-icon': { + position: 'relative', + marginRight: theme.spacing(0.5), + + '.gf-form-input-icon': { + position: 'absolute', + top: '8px', + fontSize: theme.typography.size.lg, + left: '10px', + color: theme.colors.text.disabled, + }, + + '> input': { + paddingLeft: '35px', + + '&:focus + .gf-form-input-icon': { + color: theme.colors.text.secondary, + }, + }, + + '.Select--multi .Select-multi-value-wrapper, .Select-placeholder': { + paddingLeft: '30px', + }, + }, + + '.gf-form-disabled': { + color: theme.colors.text.secondary, + + '.gf-form-select-wrapper::after': { + color: theme.colors.text.secondary, + }, + + 'a, .gf-form-input': { + color: theme.colors.text.secondary, + }, + }, + + '.gf-form-group': { + marginBottom: theme.spacing(5), + }, + '.gf-form-inline': { + display: 'flex', + flexDirection: 'row', + flexWrap: 'wrap', + alignContent: 'flex-start', + + '&--nowrap': { + flexWrap: 'nowrap', + }, + + '&--xs-view-flex-column': { + flexDirection: 'row', + flexWrap: 'nowrap', + [theme.breakpoints.down('sm')]: { + flexDirection: 'column', + }, + }, + + '.select-container': { + marginRight: theme.spacing(0.5), + }, + + '.gf-form-spacing': { + marginRight: theme.spacing(0.5), + }, + }, + + '.gf-form-button-row': { + paddingTop: theme.spacing(3), + 'a, button': { + marginRight: theme.spacing(2), + }, + }, + '.gf-form-label': { + display: 'flex', + alignItems: 'center', + padding: theme.spacing(0, 1), + flexShrink: 0, + fontWeight: theme.typography.fontWeightMedium, + fontSize: theme.typography.size.sm, + backgroundColor: theme.colors.background.secondary, + height: '32px', + lineHeight: '32px', + marginRight: theme.spacing(0.5), + borderRadius: theme.shape.radius.default, + justifyContent: 'space-between', + border: 'none', + + '&--grow': { + flexGrow: 1, + }, + + '&--transparent': { + backgroundColor: 'transparent', + border: 0, + textAlign: 'right', + paddingLeft: 0, + }, + + '&--variable': { + color: theme.colors.primary.text, + background: theme.components.panel.background, + border: `1px solid ${theme.components.panel.borderColor}`, + }, + + '&--btn': { + border: 'none', + borderRadius: theme.shape.radius.default, + '&:hover': { + background: theme.colors.background.secondary, + color: theme.colors.text.primary, + }, + }, + + '&:disabled': { + color: theme.colors.text.secondary, + }, + }, + '.gf-form-label + .gf-form-label': { + marginRight: theme.spacing(0.5), + }, + '.gf-form-pre': { + display: 'block !important', + flexGrow: 1, + margin: 0, + marginRight: theme.spacing(0.5), + border: `1px solid transparent`, + borderLeft: 'none', + borderRadius: theme.shape.radius.default, + }, + '.gf-form-textarea': { + maxWidth: '650px', + }, + '.gf-form-input': { + display: 'block', + width: '100%', + height: '32px', + padding: theme.spacing(0, 1), + fontSize: theme.typography.size.md, + lineHeight: '18px', + color: theme.components.input.text, + backgroundColor: theme.components.input.background, + backgroundImage: 'none', + backgroundClip: 'padding-box', + border: `1px solid ${theme.components.input.borderColor}`, + borderRadius: theme.shape.radius.default, + marginRight: theme.spacing(0.5), + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + + // text areas should be scrollable + '&textarea': { + overflow: 'auto', + whiteSpace: 'pre-wrap', + padding: `6px ${theme.spacing(1)}`, + minHeight: '32px', + height: 'auto', + }, + + // Unstyle the caret on ``s in IE10+. - &::-ms-expand { - background-color: transparent; - border: 0; - display: none; - } - - // Customize the `:focus` state to imitate native WebKit styles. - @include form-control-focus(); - - // Placeholder - &::placeholder { - color: $input-color-placeholder; - opacity: 1; - } - - &:disabled, - &[readonly] { - background-color: $input-bg-disabled; - // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655. - opacity: 1; - } - - &:disabled { - cursor: $cursor-disabled; - } - - &.gf-size-auto { - width: auto; - } - - &--dropdown { - padding-right: $space-lg; - position: relative; - display: flex; - align-items: center; - - &::after { - position: absolute; - top: 36%; - right: 11px; - font-size: 11px; - background-color: transparent; - color: $text-color; - font: normal normal normal list.slash($font-size-sm, 1) FontAwesome; - content: '\f0d7'; - pointer-events: none; - } - } - - &--small { - padding-top: 4px; - padding-bottom: 4px; - font-size: $font-size-sm; - } - - &--plaintext { - white-space: unset; - } - - &--has-help-icon { - padding-right: $space-xl; - } -} - -.gf-form-hint { - width: 100%; -} - -.gf-form-hint-text { - display: block; - text-align: right; - padding-top: 0.5em; -} - -.gf-form-select-wrapper { - position: relative; - background-color: $input-bg; - margin-right: $space-xs; - - .gf-form-select-icon { - position: absolute; - z-index: 1; - left: $input-padding; - top: 50%; - margin-top: -7px; - - + .gf-form-input { - position: relative; - z-index: 2; - padding-left: $space-xl; - background-color: transparent; - - option { - // Firefox - color: $black; - } - } - } - - .gf-form-input { - margin-right: 0; - line-height: $input-height; - } - - select.gf-form-input { - text-indent: 0.01px; - text-overflow: ''; - padding-right: $space-xl; - appearance: none; - - &:-moz-focusring { - outline: none; - color: transparent; - text-shadow: 0 0 0 $text-color; - } - - &.ng-empty { - color: $text-color-weak; - } - } - - &::after { - position: absolute; - top: 36%; - right: 11px; - background-color: transparent; - color: $text-color; - font: normal normal normal list.slash($font-size-sm, 1) FontAwesome; - content: '\f0d7'; - pointer-events: none; - font-size: 11px; - } - - &--has-help-icon { - &::after { - right: $space-xl; - } - } -} - -.gf-form--v-stretch { - align-items: stretch; -} - -.gf-form-btn { - padding: $input-padding; - margin-right: $space-xs; - line-height: $input-line-height; - font-size: $font-size-sm; - - flex-shrink: 0; - flex-grow: 0; -} - -.natural-language-input { - &input[type='number'] { - font-size: $font-size-base; - line-height: $input-line-height; - margin: -6px -5px 0 5px; - padding: $space-xs; - } -} - -.gf-form-dropdown-typeahead { - //margin-right: $space-xs; ? - position: relative; - - &::after { - position: absolute; - top: 35%; - right: $space-sm; - background-color: transparent; - color: $input-color; - font: normal normal normal list.slash($font-size-sm, 1) FontAwesome; - content: '\f0d7'; - pointer-events: none; - font-size: 11px; - } -} - -.gf-form-help-icon { - flex-grow: 0; - color: $text-color-weak; - - &--bold { - color: $text-color-emphasis; - padding-left: 0; - } - - &--right-absolute { - position: absolute; - right: $spacer; - top: 6px; - } - - &--right-normal { - float: right; - } - - &--header { - margin-bottom: $space-xxs; - } - - &--small-padding { - padding-left: 4px; - } - - &:hover { - color: $text-color; - } -} - -select.gf-form-input ~ .gf-form-help-icon { - right: 10px; -} - -.gf-form-icon--right-absolute { - position: absolute; - right: $spacer; - top: 10px; - color: $text-muted; -} - -.cta-form { - position: relative; - padding: $space-lg; - background-color: $empty-list-cta-bg; - margin-bottom: $space-lg; - border-top: 3px solid $green-base; -} - -.cta-form__close { - background: transparent; - padding: 4px 8px 4px 9px; - border: none; - position: absolute; - right: 0; - top: -2px; - font-size: $font-size-md; - - &:hover { - color: $text-color-strong; - } -} - -.cta-form__bar { - display: flex; - align-items: center; - align-content: center; - margin-bottom: 20px; -} - -.cta-form__bar-header { - font-size: $font-size-h4; - padding-right: 20px; -} From 32e4ea3b3c58033a755d3d28c6c782e7c51774a7 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:21:20 +0300 Subject: [PATCH 020/229] I18n: Download translations from Crowdin (#91796) New Crowdin translations by GitHub Action Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- public/locales/de-DE/grafana.json | 47 +++++++++++++++++++++-------- public/locales/es-ES/grafana.json | 47 +++++++++++++++++++++-------- public/locales/fr-FR/grafana.json | 47 +++++++++++++++++++++-------- public/locales/pt-BR/grafana.json | 47 +++++++++++++++++++++-------- public/locales/zh-Hans/grafana.json | 45 +++++++++++++++++++-------- 5 files changed, 173 insertions(+), 60 deletions(-) diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index ba90501e3ac..c1a4563946a 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -106,6 +106,7 @@ "export-all": "", "view": "" }, + "contact-point": "", "contact-points": { "delivery-duration": "", "last-delivery-attempt": "", @@ -136,7 +137,21 @@ "saving": "" }, "policies": { + "default-policy": { + "description": "", + "title": "" + }, + "generated-policies": "", "metadata": { + "active-time": "", + "delivered-to": "", + "grouped-by": "", + "grouping": { + "none": "", + "single-group": "" + }, + "inherited": "", + "mute-time": "", "timingOptions": { "groupInterval": { "description": "", @@ -150,8 +165,15 @@ "description": "", "label": "" } - } - } + }, + "n-instances_one": "", + "n-instances_other": "" + }, + "new-child": "", + "new-policy": "", + "no-matchers": "", + "n-more-policies_one": "", + "n-more-policies_other": "" }, "provisioning": { "badge-tooltip-provenance": "", @@ -1911,16 +1933,7 @@ "dismissable-button": "Schließen" }, "scopes": { - "filters": { - "apply": "", - "cancel": "", - "input": { - "placeholder": "", - "removeAll": "" - }, - "title": "" - }, - "suggestedDashboards": { + "dashboards": { "loading": "", "noResultsForFilter": "", "noResultsForFilterClear": "", @@ -1929,9 +1942,19 @@ "search": "", "toggle": { "collapse": "", + "disabled": "", "expand": "" } }, + "selector": { + "apply": "", + "cancel": "", + "input": { + "placeholder": "", + "removeAll": "" + }, + "title": "" + }, "tree": { "collapse": "", "expand": "", diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index fa2c5b4fd11..56de05e7067 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -106,6 +106,7 @@ "export-all": "", "view": "" }, + "contact-point": "", "contact-points": { "delivery-duration": "", "last-delivery-attempt": "", @@ -136,7 +137,21 @@ "saving": "" }, "policies": { + "default-policy": { + "description": "", + "title": "" + }, + "generated-policies": "", "metadata": { + "active-time": "", + "delivered-to": "", + "grouped-by": "", + "grouping": { + "none": "", + "single-group": "" + }, + "inherited": "", + "mute-time": "", "timingOptions": { "groupInterval": { "description": "", @@ -150,8 +165,15 @@ "description": "", "label": "" } - } - } + }, + "n-instances_one": "", + "n-instances_other": "" + }, + "new-child": "", + "new-policy": "", + "no-matchers": "", + "n-more-policies_one": "", + "n-more-policies_other": "" }, "provisioning": { "badge-tooltip-provenance": "", @@ -1911,16 +1933,7 @@ "dismissable-button": "Cerrar" }, "scopes": { - "filters": { - "apply": "", - "cancel": "", - "input": { - "placeholder": "", - "removeAll": "" - }, - "title": "" - }, - "suggestedDashboards": { + "dashboards": { "loading": "", "noResultsForFilter": "", "noResultsForFilterClear": "", @@ -1929,9 +1942,19 @@ "search": "", "toggle": { "collapse": "", + "disabled": "", "expand": "" } }, + "selector": { + "apply": "", + "cancel": "", + "input": { + "placeholder": "", + "removeAll": "" + }, + "title": "" + }, "tree": { "collapse": "", "expand": "", diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index 19c0e8da1e1..b8614545a5b 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -106,6 +106,7 @@ "export-all": "", "view": "" }, + "contact-point": "", "contact-points": { "delivery-duration": "", "last-delivery-attempt": "", @@ -136,7 +137,21 @@ "saving": "" }, "policies": { + "default-policy": { + "description": "", + "title": "" + }, + "generated-policies": "", "metadata": { + "active-time": "", + "delivered-to": "", + "grouped-by": "", + "grouping": { + "none": "", + "single-group": "" + }, + "inherited": "", + "mute-time": "", "timingOptions": { "groupInterval": { "description": "", @@ -150,8 +165,15 @@ "description": "", "label": "" } - } - } + }, + "n-instances_one": "", + "n-instances_other": "" + }, + "new-child": "", + "new-policy": "", + "no-matchers": "", + "n-more-policies_one": "", + "n-more-policies_other": "" }, "provisioning": { "badge-tooltip-provenance": "", @@ -1911,16 +1933,7 @@ "dismissable-button": "Fermer" }, "scopes": { - "filters": { - "apply": "", - "cancel": "", - "input": { - "placeholder": "", - "removeAll": "" - }, - "title": "" - }, - "suggestedDashboards": { + "dashboards": { "loading": "", "noResultsForFilter": "", "noResultsForFilterClear": "", @@ -1929,9 +1942,19 @@ "search": "", "toggle": { "collapse": "", + "disabled": "", "expand": "" } }, + "selector": { + "apply": "", + "cancel": "", + "input": { + "placeholder": "", + "removeAll": "" + }, + "title": "" + }, "tree": { "collapse": "", "expand": "", diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index 3ec327ece69..9322802313d 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -106,6 +106,7 @@ "export-all": "", "view": "" }, + "contact-point": "", "contact-points": { "delivery-duration": "", "last-delivery-attempt": "", @@ -136,7 +137,21 @@ "saving": "" }, "policies": { + "default-policy": { + "description": "", + "title": "" + }, + "generated-policies": "", "metadata": { + "active-time": "", + "delivered-to": "", + "grouped-by": "", + "grouping": { + "none": "", + "single-group": "" + }, + "inherited": "", + "mute-time": "", "timingOptions": { "groupInterval": { "description": "", @@ -150,8 +165,15 @@ "description": "", "label": "" } - } - } + }, + "n-instances_one": "", + "n-instances_other": "" + }, + "new-child": "", + "new-policy": "", + "no-matchers": "", + "n-more-policies_one": "", + "n-more-policies_other": "" }, "provisioning": { "badge-tooltip-provenance": "", @@ -1911,16 +1933,7 @@ "dismissable-button": "Fechar" }, "scopes": { - "filters": { - "apply": "", - "cancel": "", - "input": { - "placeholder": "", - "removeAll": "" - }, - "title": "" - }, - "suggestedDashboards": { + "dashboards": { "loading": "", "noResultsForFilter": "", "noResultsForFilterClear": "", @@ -1929,9 +1942,19 @@ "search": "", "toggle": { "collapse": "", + "disabled": "", "expand": "" } }, + "selector": { + "apply": "", + "cancel": "", + "input": { + "placeholder": "", + "removeAll": "" + }, + "title": "" + }, "tree": { "collapse": "", "expand": "", diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 7a840e4061f..dad8bf901ce 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -106,6 +106,7 @@ "export-all": "", "view": "" }, + "contact-point": "", "contact-points": { "delivery-duration": "", "last-delivery-attempt": "", @@ -135,7 +136,21 @@ "saving": "" }, "policies": { + "default-policy": { + "description": "", + "title": "" + }, + "generated-policies": "", "metadata": { + "active-time": "", + "delivered-to": "", + "grouped-by": "", + "grouping": { + "none": "", + "single-group": "" + }, + "inherited": "", + "mute-time": "", "timingOptions": { "groupInterval": { "description": "", @@ -149,8 +164,13 @@ "description": "", "label": "" } - } - } + }, + "n-instances_other": "" + }, + "new-child": "", + "new-policy": "", + "no-matchers": "", + "n-more-policies_other": "" }, "provisioning": { "badge-tooltip-provenance": "", @@ -1902,16 +1922,7 @@ "dismissable-button": "关闭" }, "scopes": { - "filters": { - "apply": "", - "cancel": "", - "input": { - "placeholder": "", - "removeAll": "" - }, - "title": "" - }, - "suggestedDashboards": { + "dashboards": { "loading": "", "noResultsForFilter": "", "noResultsForFilterClear": "", @@ -1920,9 +1931,19 @@ "search": "", "toggle": { "collapse": "", + "disabled": "", "expand": "" } }, + "selector": { + "apply": "", + "cancel": "", + "input": { + "placeholder": "", + "removeAll": "" + }, + "title": "" + }, "tree": { "collapse": "", "expand": "", From 369cd6f81e59048d4f8518530ff6ab8b505ff280 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Mon, 12 Aug 2024 13:22:14 +0100 Subject: [PATCH 021/229] Chore: Migrate `dashboard_grid` styles to emotion (#91673) migrate dashboard grid styles to emotion --- .../src/themes/GlobalStyles/GlobalStyles.tsx | 2 + .../src/themes/GlobalStyles/dashboardGrid.ts | 68 +++++++++++ .../dashboard/containers/DashboardPage.tsx | 46 +++++++- public/sass/_grafana.scss | 1 - public/sass/components/_dashboard_grid.scss | 106 ------------------ 5 files changed, 112 insertions(+), 111 deletions(-) create mode 100644 packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts delete mode 100644 public/sass/components/_dashboard_grid.scss diff --git a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx index 59587af744d..6434df3cdb6 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx +++ b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx @@ -7,6 +7,7 @@ import { getAlertingStyles } from './alerting'; import { getAgularPanelStyles } from './angularPanelStyles'; import { getCardStyles } from './card'; import { getCodeStyles } from './code'; +import { getDashboardGridStyles } from './dashboardGrid'; import { getDashDiffStyles } from './dashdiff'; import { getElementStyles } from './elements'; import { getExtraStyles } from './extra'; @@ -35,6 +36,7 @@ export function GlobalStyles() { getAlertingStyles(theme), getCodeStyles(theme), getDashDiffStyles(theme), + getDashboardGridStyles(theme), getElementStyles(theme), getExtraStyles(theme), getFilterTableStyles(theme), diff --git a/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts b/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts new file mode 100644 index 00000000000..bbd6d6da876 --- /dev/null +++ b/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts @@ -0,0 +1,68 @@ +import { css } from '@emotion/react'; + +import { GrafanaTheme2 } from '@grafana/data'; + +export function getDashboardGridStyles(theme: GrafanaTheme2) { + return css({ + '.react-resizable-handle': { + // this needs to use visibility and not display none in order not to cause resize flickering + visibility: 'hidden', + }, + + '.react-grid-item, #grafana-portal-container': { + touchAction: 'initial !important', + + '&:hover': { + '.react-resizable-handle': { + visibility: 'visible', + }, + }, + }, + + [theme.breakpoints.down('md')]: { + '.react-grid-item': { + display: 'block !important', + transitionProperty: 'none !important', + // can't avoid type assertion here due to !important + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + position: 'unset !important' as 'unset', + transform: 'translate(0px, 0px) !important', + marginBottom: theme.spacing(2), + }, + '.panel-repeater-grid-item': { + height: 'auto !important', + }, + }, + + '.react-grid-item.react-grid-placeholder': { + boxShadow: `0 0 4px ${theme.colors.primary.border} !important`, + background: `${theme.colors.primary.transparent} !important`, + zIndex: '-1 !important', + opacity: 'unset !important', + }, + + '.react-grid-item > .react-resizable-handle::after': { + borderRight: `2px solid ${theme.isDark ? theme.v1.palette.gray1 : theme.v1.palette.gray3} !important`, + borderBottom: `2px solid ${theme.isDark ? theme.v1.palette.gray1 : theme.v1.palette.gray3} !important`, + }, + + // Hack for preventing panel menu overlapping. + '.react-grid-item.resizing.panel, .react-grid-item.panel.dropdown-menu-open, .react-grid-item.react-draggable-dragging.panel': + { + zIndex: theme.zIndex.dropdown, + }, + + // Disable animation on initial rendering and enable it when component has been mounted. + '.react-grid-item.cssTransforms': { + transitionProperty: 'none !important', + }, + + [theme.transitions.handleMotion('no-preference')]: { + '.react-grid-layout--enable-move-animations': { + '.react-grid-item.cssTransforms': { + transitionProperty: 'transform !important', + }, + }, + }, + }); +} diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 27e356bb9fa..310bebf2b4b 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -1,8 +1,8 @@ -import { cx } from '@emotion/css'; +import { css, cx } from '@emotion/css'; import { PureComponent } from 'react'; import { connect, ConnectedProps } from 'react-redux'; -import { NavModel, NavModelItem, TimeRange, PageLayoutType, locationUtil } from '@grafana/data'; +import { NavModel, NavModelItem, TimeRange, PageLayoutType, locationUtil, GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { config, locationService } from '@grafana/runtime'; import { Themeable2, withTheme2 } from '@grafana/ui'; @@ -44,6 +44,9 @@ import { initDashboard } from '../state/initDashboard'; import { DashboardPageRouteParams, DashboardPageRouteSearchParams } from './types'; +import 'react-grid-layout/css/styles.css'; +import 'react-resizable/css/styles.css'; + export const mapStateToProps = (state: StoreState) => ({ initPhase: state.dashboard.initPhase, initError: state.dashboard.initError, @@ -79,6 +82,40 @@ export interface State { sectionNav?: NavModel; } +const getStyles = (theme: GrafanaTheme2) => ({ + fullScreenPanel: css({ + '.react-grid-layout': { + height: 'auto !important', + transitionProperty: 'none', + }, + '.react-grid-item': { + display: 'none !important', + transitionProperty: 'none !important', + + '&--fullscreen': { + display: 'block !important', + // can't avoid type assertion here due to !important + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + position: 'unset !important' as 'unset', + transform: 'translate(0px, 0px) !important', + }, + }, + + // Disable grid interaction indicators in fullscreen panels + '.panel-header:hover': { + backgroundColor: 'inherit', + }, + + '.panel-title-container': { + cursor: 'pointer', + }, + + '.react-resizable-handle': { + display: 'none', + }, + }), +}); + export class UnthemedDashboardPage extends PureComponent { declare context: GrafanaContextType; static contextType = GrafanaContext; @@ -328,9 +365,10 @@ export class UnthemedDashboardPage extends PureComponent { }; render() { - const { dashboard, initError, queryParams } = this.props; + const { dashboard, initError, queryParams, theme } = this.props; const { editPanel, viewPanel, updateScrollTop, pageNav, sectionNav } = this.state; const kioskMode = getKioskMode(this.props.queryParams); + const styles = getStyles(theme); if (!dashboard || !pageNav || !sectionNav) { return ; @@ -342,7 +380,7 @@ export class UnthemedDashboardPage extends PureComponent { const showToolbar = kioskMode !== KioskMode.Full && !queryParams.editview; const pageClassName = cx({ - 'panel-in-fullscreen': Boolean(viewPanel), + [styles.fullScreenPanel]: Boolean(viewPanel), 'page-hidden': Boolean(queryParams.editview || editPanel), }); diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 6c32eb073a9..f9eedb5bf43 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -14,7 +14,6 @@ // COMPONENTS @import 'components/buttons'; @import 'components/dropdown'; -@import 'components/dashboard_grid'; // ANGULAR @import 'angular'; diff --git a/public/sass/components/_dashboard_grid.scss b/public/sass/components/_dashboard_grid.scss deleted file mode 100644 index 7185a2a9be2..00000000000 --- a/public/sass/components/_dashboard_grid.scss +++ /dev/null @@ -1,106 +0,0 @@ -@import 'react-grid-layout/css/styles'; -@import 'react-resizable/css/styles'; - -.react-resizable-handle { - // this needs to use visibility and not display none in order not to cause resize flickering - visibility: hidden; -} - -.react-grid-item, -#grafana-portal-container { - touch-action: initial !important; - - &:hover { - .react-resizable-handle { - visibility: visible; - } - } -} - -.panel-in-fullscreen { - .react-grid-layout { - height: auto !important; - } - .react-grid-item { - display: none !important; - transition-property: none !important; - - &--fullscreen { - display: block !important; - position: unset !important; - transform: translate(0px, 0px) !important; - } - } - - // Disable grid interaction indicators in fullscreen panels - .panel-header:hover { - background-color: inherit; - } - - .panel-title-container { - cursor: pointer; - } - - .react-resizable-handle { - display: none; - } - - // the react-grid has a height transition - .react-grid-layout { - transition-property: none; - } -} - -@include media-breakpoint-down(sm) { - .react-grid-item { - display: block !important; - transition-property: none !important; - position: unset !important; - transform: translate(0px, 0px) !important; - margin-bottom: $space-md; - } - .panel-repeater-grid-item { - height: auto !important; - } -} - -.react-grid-item.react-grid-placeholder { - box-shadow: $panel-grid-placeholder-shadow; - background: $panel-grid-placeholder-bg; - z-index: -1; - opacity: unset; -} - -.theme-dark { - .react-grid-item > .react-resizable-handle::after { - border-right: 2px solid $gray-1; - border-bottom: 2px solid $gray-1; - } -} - -.theme-light { - .react-grid-item > .react-resizable-handle::after { - border-right: 2px solid $gray-3; - border-bottom: 2px solid $gray-3; - } -} - -// Hack for preventing panel menu overlapping. -.react-grid-item.resizing.panel, -.react-grid-item.panel.dropdown-menu-open, -.react-grid-item.react-draggable-dragging.panel { - z-index: $zindex-dropdown; -} - -// Disable animation on initial rendering and enable it when component has been mounted. -.react-grid-item.cssTransforms { - transition-property: none; -} - -@media (prefers-reduced-motion: no-preference) { - .react-grid-layout--enable-move-animations { - .react-grid-item.cssTransforms { - transition-property: transform; - } - } -} From c9ddc688a2b2c4ebb49e8ecf71dc01e33704da32 Mon Sep 17 00:00:00 2001 From: Vijay Samuel Date: Mon, 12 Aug 2024 18:01:39 +0530 Subject: [PATCH 022/229] Prometheus: Add support to make parallel queries (#90316) * Add support for prometheus datasource to make parallel queries * Incorporate review comments * Update pkg/promlib/querydata/request.go Co-authored-by: ismail simsek * Fix lint * Add parallel queries behind feature flag * Fixing lint issue * Update go.mod * Update pkg/promlib/querydata/request.go * Update pkg/promlib/querydata/request.go --------- Co-authored-by: ismail simsek Co-authored-by: Charandas <542168+charandas@users.noreply.github.com> --- go.work.sum | 99 ++++-------- .../src/types/featureToggles.gen.ts | 1 + pkg/promlib/go.mod | 1 + pkg/promlib/go.sum | 2 + pkg/promlib/querydata/request.go | 146 +++++++++++++----- pkg/services/featuremgmt/registry.go | 7 + pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 + pkg/services/featuremgmt/toggles_gen.json | 12 ++ 9 files changed, 168 insertions(+), 105 deletions(-) diff --git a/go.work.sum b/go.work.sum index 63adaa4e545..dc231d9c2a7 100644 --- a/go.work.sum +++ b/go.work.sum @@ -221,6 +221,7 @@ github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw= github.com/aws/aws-lambda-go v1.13.3 h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwRDzY= +github.com/aws/aws-sdk-go v1.44.321/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1 h1:w/fPGB0t5rWwA43mux4e9ozFSH5zF1moQemlA131PWc= github.com/aws/aws-sdk-go-v2/service/kms v1.16.3 h1:nUP29LA4GZZPihNSo5ZcF4Rl73u+bN5IBRnrQA0jFK4= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4 h1:EmIEXOjAdXtxa2OGM1VAajZV/i06Q8qd4kBpJd9/p1k= @@ -250,7 +251,6 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= @@ -275,8 +275,6 @@ github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHo github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf h1:CAKfRE2YtTUIjjh1bkBtyYFaUT/WmOqsJjgtihT0vMI= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/crewjam/httperr v0.2.0 h1:b2BfXR8U3AlIHwNeFFvZ+BV1LFvKLlzMjzaTnZMybNo= @@ -325,14 +323,11 @@ github.com/elastic/go-sysinfo v1.11.2 h1:mcm4OSYVMyws6+n2HIVMGkln5HOpo5Ie1ZmbbNn github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ= 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 v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/expr-lang/expr v1.16.2 h1:JvMnzUs3LeVHBvGFcXYmXo+Q6DPDmzrlcSBO6Wy3w4s= github.com/expr-lang/expr v1.16.2/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= -github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= @@ -340,10 +335,10 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTg github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2 h1:cZqz+yOJ/R64LcKjNQOdARott/jP7BnUQ9Ah7KaZCvw= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8 h1:a9ENSRDFBUPkJ5lCgVZh26+ZbGyoVJG7yb5SSzF5H54= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/fsouza/fake-gcs-server v1.7.0 h1:Un0BXUXrRWYSmYyC1Rqm2e2WJfTPyDy/HGMz31emTi8= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= @@ -359,13 +354,10 @@ github.com/go-fonts/stix v0.1.0 h1:UlZlgrvvmT/58o573ot7NFw0vZasZ5I6bcIft/oMdgg= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-ini/ini v1.25.4 h1:Mujh4R/dH6YL8bxuISne3xX2+qcQ9p0IxKAP6ExWoUo= -github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -384,6 +376,7 @@ github.com/goccmack/gocc v0.0.0-20230228185258-2292f9e40198/go.mod h1:DTh/Y2+Nbn github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54= github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4 h1:vF83LI8tAakwEwvWZtrIEx7pOySacl2TOxx6eXk4ePo= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= +github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4= @@ -395,6 +388,7 @@ github.com/google/go-jsonnet v0.18.0/go.mod h1:C3fTzyVJDslXdiTqw/bTFk7vSGyCtH3MG github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9 h1:OF1IPgv+F4NmqmJ98KTjdN97Vs1JxDPB3vbmYzV2dpk= github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE= github.com/google/go-replayers/httpreplay v1.1.1 h1:H91sIMlt1NZzN7R+/ASswyouLJfW0WLW7fhyUFvDEkY= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= @@ -407,13 +401,10 @@ github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= -github.com/grafana/alerting v0.0.0-20240712142914-5558735b4462 h1:MWpvVoPcSej4YfxSIuAllr9vg0UgVEG5CQifD5fK+ps= -github.com/grafana/alerting v0.0.0-20240712142914-5558735b4462/go.mod h1:DLj8frbtCaITljC2jc0L85JQViPF3mPfOSiYhm1osso= -github.com/grafana/authlib v0.0.0-20240611075137-331cbe4e840f/go.mod h1:+MjD5sxxgLOIvw0ox18wJmjBzz8tOECo7quiiZAmgJY= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grafana/authlib/claims v0.0.0-20240809095826-8eb5495c0b2a/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= -github.com/grafana/grafana-plugin-sdk-go v0.235.0/go.mod h1:6n9LbrjGL3xAATntYVNcIi90G9BVHRJjzHKz5FXVfWw= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b h1:HCbWyVL6vi7gxyO76gQksSPH203oBJ1MJ3JcG1OQlsg= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b/go.mod h1:01sXtHoRwI8W324IPAzuxDFOmALqYLCOhvSC2fUHWXc= +github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU= +github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= @@ -422,11 +413,17 @@ github.com/hamba/avro/v2 v2.17.2 h1:6PKpEWzJfNnvBgn7m2/8WYaDOUASxfDU+Jyb4ojDgFY= github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc= github.com/hanwen/go-fuse/v2 v2.1.0 h1:+32ffteETaLYClUj0a3aHjZ1hOPxxaNEHiZiujuDaek= +github.com/hashicorp/consul/api v1.15.3/go.mod h1:/g/qgcoBcEXALCNZgRRisyTW0nY86++L0KbeAMXYCeY= +github.com/hashicorp/consul/sdk v0.11.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= github.com/hashicorp/consul/sdk v0.16.0 h1:SE9m0W6DEfgIVCJX7xU+iv/hUl4m/nxqMTnCdMxDpJ8= +github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= +github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= +github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hudl/fargo v1.4.0 h1:ZDDILMbB37UlAVLlWcJ2Iz1XuahZZTDZfdCKeclfq2s= @@ -434,7 +431,6 @@ github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSAS github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 h1:KwWnWVWCNtNq/ewIX7HIKnELmEx2nDP42yskD/pi7QE= github.com/influxdata/influxdb v1.7.6 h1:8mQ7A/V+3noMGCt/P9pD09ISaiz9XvgCk303UYA3gcs= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig= -github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= @@ -526,13 +522,14 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvls github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= +github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mithrandie/readline-csvq v1.3.0 h1:VTJEOGouJ8j27jJCD4kBBbNTxM0OdBvE1aY1tMhlqE8= github.com/mithrandie/readline-csvq v1.3.0/go.mod h1:FKyYqDgf/G4SNov7SMFXRWO6LQLXIOeTog/NB97FZl0= -github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA= github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY/JU= github.com/mostynb/go-grpc-compression v1.2.2 h1:XaDbnRvt2+1vgr0b/l0qh4mJAfIxE0bKXtz2Znl3GGI= @@ -550,6 +547,7 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 h1:dOYG7LS/WK00RWZc8XGgcUTlTxpp3mKhdR2Q9z9HbXM= github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= +github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc= github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/kafkaexporter v0.97.0 h1:8GH8y3Cq54Ey6He9tyhcVYLfG4TEs/7pp3s6934zNKA= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/kafkaexporter v0.97.0/go.mod h1:QBHXt+tHds39B4xGyBkbOx2TST+p8JLWBiXbKKAhNss= @@ -612,19 +610,18 @@ github.com/performancecopilot/speed/v4 v4.0.0 h1:VxEDCmdkfbQYDlcr/GC9YoN9PQ6p8ul github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/phpdave11/gofpdf v1.4.2 h1:KPKiIbfwbvC/wOncwhrpRdXVj2CZTCFlw4wnoyjtHfQ= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= +github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= +github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE= -github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= -github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/prometheus/common v0.54.0/go.mod h1:/TQgMJP5CuVYveyT7n/0Ix8yLNNXy9yRSkhnLTHPDIQ= github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM= -github.com/prometheus/procfs v0.14.0/go.mod h1:XL+Iwz8k8ZabyZfMFHPiilCniixqQarAy5Mu67pHlNQ= +github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97/go.mod h1:LoBCZeRh+5hX+fSULNyFnagYlQG/gBsyA/deNzROkq8= github.com/prometheus/statsd_exporter v0.26.0 h1:SQl3M6suC6NWQYEzOvIv+EF6dAMYEqIuZy+o4H9F5Ig= github.com/prometheus/statsd_exporter v0.26.0/go.mod h1:GXFLADOmBTVDrHc7b04nX8ooq3azG61pnECNqT7O5DM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= @@ -653,16 +650,12 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= -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/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e h1:mOtuXaRAbVZsxAHVdPR3IjfmN8T1h2iczJLynhLybf8= -github.com/stretchr/objx v0.5.1/go.mod h1:/iHQpkQwBD6DLUmQ4pE+s1TXdob1mORJ4/UFdrifcy0= github.com/substrait-io/substrait-go v0.4.2 h1:buDnjsb3qAqTaNbOR7VKmNgXf4lYQxWEcnSGUWBtmN8= github.com/tdewolff/minify/v2 v2.12.9 h1:dvn5MtmuQ/DFMwqf5j8QhEVpPX6fi3WGImhv8RUB4zA= github.com/tdewolff/minify/v2 v2.12.9/go.mod h1:qOqdlDfL+7v0/fyymB+OP497nIxJYSvX4MQWA8OoiXU= @@ -683,6 +676,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/uber-go/atomic v1.4.0 h1:yOuPqEq4ovnhEjpHmfFwsqBXDYbQeT6Nb0bwD6XnD5o= github.com/uber-go/atomic v1.4.0/go.mod h1:/Ct5t2lcmbJ4OSe/waGBoaVvVqtO0bmtfVNex1PFV8g= +github.com/uber/jaeger-client-go v2.28.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/vburenin/ifacemaker v1.2.1 h1:3Vq8B/bfBgjWTkv+jDg4dVL1KHt3k1K4lO7XRxYA2sk= @@ -720,6 +715,7 @@ github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1/go.mod h1:udNPW8eupyH/EZocecFmaSNJ github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= +github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= @@ -728,9 +724,6 @@ github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= -go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= -go.etcd.io/etcd/client/pkg/v3 v3.5.10/go.mod h1:DYivfIviIuQ8+/lCq4vcxuseg2P2XbHygkKwFo9fc8U= -go.etcd.io/etcd/client/v3 v3.5.10/go.mod h1:RVeBnDz2PUEZqTpgqwAtUd8nAPf5kjyFyND7P1VkOKc= go.opentelemetry.io/collector v0.97.0 h1:qyOju13byHIKEK/JehmTiGMj4pFLa4kDyrOCtTmjHU0= go.opentelemetry.io/collector v0.97.0/go.mod h1:V6xquYAaO2VHVu4DBK28JYuikRdZajh7DH5Vl/Y8NiA= go.opentelemetry.io/collector/component v0.97.0 h1:vanKhXl5nptN8igRH4PqVYHOILif653vaPIKv6LCZCI= @@ -796,13 +789,9 @@ go.opentelemetry.io/collector/service v0.95.0/go.mod h1:4yappQmDE5UZmLE9wwtj6IPM go.opentelemetry.io/contrib/config v0.4.0 h1:Xb+ncYOqseLroMuBesGNRgVQolXcXOhMj7EhGwJCdHs= go.opentelemetry.io/contrib/config v0.4.0/go.mod h1:drNk2xRqLWW4/amk6Uh1S+sDAJTc7bcEEN1GfJzj418= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.44.0/go.mod h1:SeQhzAEccGVZVEy7aH87Nh0km+utSpo1pTv6eMMop48= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc= go.opentelemetry.io/contrib/propagators/b3 v1.23.0 h1:aaIGWc5JdfRGpCafLRxMJbD65MfTa206AwSKkvGS0Hg= go.opentelemetry.io/contrib/propagators/b3 v1.23.0/go.mod h1:Gyz7V7XghvwTq+mIhLFlTgcc03UDroOg8vezs4NLhwU= -go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel/bridge/opencensus v1.26.0 h1:DZzxj9QjznMVoehskOJnFP2gsTCWtDTFBDvFhPAY7nc= go.opentelemetry.io/otel/bridge/opencensus v1.26.0/go.mod h1:rJiX0KrF5m8Tm1XE8jLczpAv5zUaDcvhKecFG0ZoFG4= go.opentelemetry.io/otel/bridge/opentracing v1.26.0 h1:Q/dHj0DOhfLMAs5u5ucAbC7gy66x9xxsZRLpHCJ4XhI= @@ -811,74 +800,53 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1 h1:ZqR go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1/go.mod h1:D7ynngPWlGJrqyGSDOdscuv7uqttfCE3jcBvffDv9y4= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.23.1 h1:q/Nj5/2TZRIt6PderQ9oU0M00fzoe8UZuINGw6ETGTw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.23.1/go.mod h1:DTE9yAu6r08jU3xa68GiSeI7oRcSEQ2RpKbbQGO+dWM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.26.0/go.mod h1:wnJIG4fOqyynOnnQF/eQb4/16VlX2EJAHhHgqIqWfAo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= -go.opentelemetry.io/otel/exporters/prometheus v0.37.0 h1:NQc0epfL0xItsmGgSXgfbH2C1fq2VLXkZoDFsfRNHpc= -go.opentelemetry.io/otel/exporters/prometheus v0.37.0/go.mod h1:hB8qWjsStK36t50/R0V2ULFb4u95X/Q6zupXLgvjTh8= go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ= go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.23.1 h1:C8r95vDR125t815KD+b1tI0Fbc1pFnwHTBxkbIZ6Szc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.23.1/go.mod h1:Qr0qomr64jentMtOjWMbtYeJMSuMSlsPEjmnRA2sWZ4= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= -go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= -go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= -go.opentelemetry.io/otel/sdk/metric v0.39.0 h1:Kun8i1eYf48kHH83RucG93ffz0zGV1sh46FAScOTuDI= -go.opentelemetry.io/otel/sdk/metric v0.39.0/go.mod h1:piDIRgjcK7u0HCL5pCA4e74qpK/jk3NiUoAHATVAmiI= go.opentelemetry.io/otel/sdk/metric v1.26.0 h1:cWSks5tfriHPdWFnl+qpX3P681aAYqlZHcAyHw5aU9Y= go.opentelemetry.io/otel/sdk/metric v1.26.0/go.mod h1:ClMFFknnThJCksebJwz7KIyEDHO+nTB6gK8obLy8RyE= -go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= -go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= +go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4= google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= -google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa h1:wBkzraZsSqhj1M4L/nMrljUU6XasJkgHvUsq8oRGwF0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= -google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= @@ -893,16 +861,12 @@ gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= -k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6 h1:WN8Lymy+dCTDHgn4vhUSNIB6U+0sDiv/c9Zdr0UeAnI= -k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6/go.mod h1:l0ukbPS0lwFxOzSq5ZqjutzF+5IL2TLp495PswRPSZk= k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= @@ -919,5 +883,4 @@ rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= rsc.io/sampler v1.3.0 h1:7uVkIFmeBqHfdjD+gZwtXXI+RODJ2Wc4O7MPEh/QiW4= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0/go.mod h1:VHVDI/KrK4fjnV61bE2g3sA7tiETLn8sooImelsCx3Y= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 h1:ucqkfpjg9WzSUubAO62csmucvxl4/JeW3F4I4909XkM= diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index b8bd9eff3d6..ec4c39521c1 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -59,6 +59,7 @@ export interface FeatureToggles { influxdbBackendMigration?: boolean; influxqlStreamingParser?: boolean; influxdbRunQueriesInParallel?: boolean; + prometheusRunQueriesInParallel?: boolean; prometheusDataplane?: boolean; lokiMetricDataplane?: boolean; lokiLogsDataplane?: boolean; diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 4701f263219..aed98618320 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -3,6 +3,7 @@ module github.com/grafana/grafana/pkg/promlib go 1.22.4 require ( + github.com/grafana/dskit v0.0.0-20240805174438-dfa83b4ed2d3 github.com/grafana/grafana-plugin-sdk-go v0.241.0 github.com/json-iterator/go v1.1.12 github.com/patrickmn/go-cache v2.1.0+incompatible diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 2212ab57a8b..70150229256 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -96,6 +96,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 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-20240805174438-dfa83b4ed2d3 h1:as4PmrFoYI1byS5JjsgPC7uSGTMh+SgS0ePv6hOyDGU= +github.com/grafana/dskit v0.0.0-20240805174438-dfa83b4ed2d3/go.mod h1:lcjGB6SuaZ2o44A9nD6p/tR4QXSPbzViRY520Gy6pTQ= github.com/grafana/grafana-plugin-sdk-go v0.241.0 h1:zBcSW9xV9gA9hD8UN+HjJtD7tESMZcaQhA1BI76MTxM= github.com/grafana/grafana-plugin-sdk-go v0.241.0/go.mod h1:2HjNwzGCfaFAyR2HGoECTwAmq8vSIn2L1/1yOt4XRS4= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= diff --git a/pkg/promlib/querydata/request.go b/pkg/promlib/querydata/request.go index b9afb827111..7865c9afe2c 100644 --- a/pkg/promlib/querydata/request.go +++ b/pkg/promlib/querydata/request.go @@ -5,21 +5,21 @@ import ( "fmt" "net/http" "regexp" + "sync" "time" + "github.com/grafana/dskit/concurrency" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/data" - "github.com/grafana/grafana-plugin-sdk-go/data/utils/maputil" - "go.opentelemetry.io/otel/trace" - "github.com/grafana/grafana-plugin-sdk-go/backend/log" "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" - + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana-plugin-sdk-go/data/utils/maputil" "github.com/grafana/grafana/pkg/promlib/client" "github.com/grafana/grafana/pkg/promlib/intervalv2" "github.com/grafana/grafana/pkg/promlib/models" "github.com/grafana/grafana/pkg/promlib/querydata/exemplar" "github.com/grafana/grafana/pkg/promlib/utils" + "go.opentelemetry.io/otel/trace" ) const legendFormatAuto = "__auto" @@ -88,22 +88,49 @@ func (s *QueryData) Execute(ctx context.Context, req *backend.QueryDataRequest) Responses: backend.Responses{}, } - cfg := backend.GrafanaConfigFromContext(ctx) - hasPromQLScopeFeatureFlag := cfg.FeatureToggles().IsEnabled("promQLScope") - hasPrometheusDataplaneFeatureFlag := cfg.FeatureToggles().IsEnabled("prometheusDataplane") + var ( + cfg = backend.GrafanaConfigFromContext(ctx) + hasPromQLScopeFeatureFlag = cfg.FeatureToggles().IsEnabled("promQLScope") + hasPrometheusDataplaneFeatureFlag = cfg.FeatureToggles().IsEnabled("prometheusDataplane") + hasPrometheusRunQueriesInParallel = cfg.FeatureToggles().IsEnabled("prometheusRunQueriesInParallel") + ) + + if hasPrometheusRunQueriesInParallel { + var ( + m sync.Mutex + ) + + concurrentQueryCount, err := req.PluginContext.GrafanaConfig.ConcurrentQueryCount() + if err != nil { + logger := s.log.FromContext(ctx) + logger.Debug(fmt.Sprintf("Concurrent Query Count read/parse error: %v", err), "prometheusRunQueriesInParallel") + concurrentQueryCount = 10 + } - for _, q := range req.Queries { - r := s.handleQuery(ctx, q, fromAlert, hasPromQLScopeFeatureFlag, hasPrometheusDataplaneFeatureFlag) - if r == nil { - continue + _ = concurrency.ForEachJob(ctx, len(req.Queries), concurrentQueryCount, func(ctx context.Context, idx int) error { + query := req.Queries[idx] + r := s.handleQuery(ctx, query, fromAlert, hasPromQLScopeFeatureFlag, hasPrometheusDataplaneFeatureFlag, true) + if r != nil { + m.Lock() + result.Responses[query.RefID] = *r + m.Unlock() + } + return nil + }) + } else { + for _, q := range req.Queries { + r := s.handleQuery(ctx, q, fromAlert, hasPromQLScopeFeatureFlag, hasPrometheusDataplaneFeatureFlag, false) + if r != nil { + result.Responses[q.RefID] = *r + } } - result.Responses[q.RefID] = *r } return &result, nil } -func (s *QueryData) handleQuery(ctx context.Context, bq backend.DataQuery, fromAlert, hasPromQLScopeFeatureFlag, hasPrometheusDataplaneFeatureFlag bool) *backend.DataResponse { +func (s *QueryData) handleQuery(ctx context.Context, bq backend.DataQuery, fromAlert, + hasPromQLScopeFeatureFlag, hasPrometheusDataplaneFeatureFlag, hasPrometheusRunQueriesInParallel bool) *backend.DataResponse { traceCtx, span := s.tracer.Start(ctx, "datasource.prometheus") defer span.End() query, err := models.Parse(span, bq, s.TimeInterval, s.intervalCalculator, fromAlert, hasPromQLScopeFeatureFlag) @@ -113,14 +140,15 @@ func (s *QueryData) handleQuery(ctx context.Context, bq backend.DataQuery, fromA } } - r := s.fetch(traceCtx, s.client, query, hasPrometheusDataplaneFeatureFlag) + r := s.fetch(traceCtx, s.client, query, hasPrometheusDataplaneFeatureFlag, hasPrometheusRunQueriesInParallel) if r == nil { s.log.FromContext(ctx).Debug("Received nil response from runQuery", "query", query.Expr) } return r } -func (s *QueryData) fetch(traceCtx context.Context, client *client.Client, q *models.Query, enablePrometheusDataplane bool) *backend.DataResponse { +func (s *QueryData) fetch(traceCtx context.Context, client *client.Client, q *models.Query, + enablePrometheusDataplane, hasPrometheusRunQueriesInParallel bool) *backend.DataResponse { logger := s.log.FromContext(traceCtx) logger.Debug("Sending query", "start", q.Start, "end", q.End, "step", q.Step, "query", q.Expr) @@ -129,37 +157,69 @@ func (s *QueryData) fetch(traceCtx context.Context, client *client.Client, q *mo Error: nil, } + var ( + wg sync.WaitGroup + m sync.Mutex + ) + if q.InstantQuery { - res := s.instantQuery(traceCtx, client, q, enablePrometheusDataplane) - dr.Error = res.Error - dr.Frames = res.Frames - dr.Status = res.Status + if hasPrometheusRunQueriesInParallel { + wg.Add(1) + go func() { + defer wg.Done() + res := s.instantQuery(traceCtx, client, q, enablePrometheusDataplane) + m.Lock() + addDataResponse(&res, dr) + m.Unlock() + }() + } else { + res := s.instantQuery(traceCtx, client, q, enablePrometheusDataplane) + addDataResponse(&res, dr) + } } if q.RangeQuery { - res := s.rangeQuery(traceCtx, client, q, enablePrometheusDataplane) - if res.Error != nil { - if dr.Error == nil { - dr.Error = res.Error - } else { - dr.Error = fmt.Errorf("%v %w", dr.Error, res.Error) - } - // When both instant and range are true, we may overwrite the status code. - // To fix this (and other things) they should come in separate http requests. - dr.Status = res.Status + if hasPrometheusRunQueriesInParallel { + wg.Add(1) + go func() { + defer wg.Done() + res := s.rangeQuery(traceCtx, client, q, enablePrometheusDataplane) + m.Lock() + addDataResponse(&res, dr) + m.Unlock() + }() + } else { + res := s.rangeQuery(traceCtx, client, q, enablePrometheusDataplane) + addDataResponse(&res, dr) } - dr.Frames = append(dr.Frames, res.Frames...) } if q.ExemplarQuery { - res := s.exemplarQuery(traceCtx, client, q, enablePrometheusDataplane) - if res.Error != nil { - // If exemplar query returns error, we want to only log it and - // continue with other results processing - logger.Error("Exemplar query failed", "query", q.Expr, "err", res.Error) + if hasPrometheusRunQueriesInParallel { + wg.Add(1) + go func() { + defer wg.Done() + res := s.exemplarQuery(traceCtx, client, q, enablePrometheusDataplane) + m.Lock() + if res.Error != nil { + // If exemplar query returns error, we want to only log it and + // continue with other results processing + logger.Error("Exemplar query failed", "query", q.Expr, "err", res.Error) + } + dr.Frames = append(dr.Frames, res.Frames...) + m.Unlock() + }() + } else { + res := s.exemplarQuery(traceCtx, client, q, enablePrometheusDataplane) + if res.Error != nil { + // If exemplar query returns error, we want to only log it and + // continue with other results processing + logger.Error("Exemplar query failed", "query", q.Expr, "err", res.Error) + } + dr.Frames = append(dr.Frames, res.Frames...) } - dr.Frames = append(dr.Frames, res.Frames...) } + wg.Wait() return dr } @@ -225,3 +285,15 @@ func (s *QueryData) exemplarQuery(ctx context.Context, c *client.Client, q *mode }() return s.parseResponse(ctx, q, res, enablePrometheusDataplaneFlag) } + +func addDataResponse(res *backend.DataResponse, dr *backend.DataResponse) { + if res.Error != nil { + if dr.Error == nil { + dr.Error = res.Error + } else { + dr.Error = fmt.Errorf("%v %w", dr.Error, res.Error) + } + dr.Status = res.Status + } + dr.Frames = append(dr.Frames, res.Frames...) +} diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 9964b58d5b6..9690b54ba1f 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -319,6 +319,13 @@ var ( FrontendOnly: false, Owner: grafanaObservabilityMetricsSquad, }, + { + Name: "prometheusRunQueriesInParallel", + Description: "Enables running Prometheus queries in parallel", + Stage: FeatureStagePrivatePreview, + FrontendOnly: false, + Owner: grafanaObservabilityMetricsSquad, + }, { Name: "prometheusDataplane", Description: "Changes responses to from Prometheus to be compliant with the dataplane specification. In particular, when this feature toggle is active, the numeric `Field.Name` is set from 'Value' to the value of the `__name__` label.", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 5206e0500f8..f866c897069 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -40,6 +40,7 @@ prometheusMetricEncyclopedia,GA,@grafana/observability-metrics,false,false,true influxdbBackendMigration,GA,@grafana/observability-metrics,false,false,true influxqlStreamingParser,experimental,@grafana/observability-metrics,false,false,false influxdbRunQueriesInParallel,privatePreview,@grafana/observability-metrics,false,false,false +prometheusRunQueriesInParallel,privatePreview,@grafana/observability-metrics,false,false,false prometheusDataplane,GA,@grafana/observability-metrics,false,false,false lokiMetricDataplane,GA,@grafana/observability-logs,false,false,false lokiLogsDataplane,experimental,@grafana/observability-logs,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 02379fbfc40..1aa69d2517b 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -171,6 +171,10 @@ const ( // Enables running InfluxDB Influxql queries in parallel FlagInfluxdbRunQueriesInParallel = "influxdbRunQueriesInParallel" + // FlagPrometheusRunQueriesInParallel + // Enables running Prometheus queries in parallel + FlagPrometheusRunQueriesInParallel = "prometheusRunQueriesInParallel" + // FlagPrometheusDataplane // Changes responses to from Prometheus to be compliant with the dataplane specification. In particular, when this feature toggle is active, the numeric `Field.Name` is set from 'Value' to the value of the `__name__` label. FlagPrometheusDataplane = "prometheusDataplane" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 0167e963b1e..e4be36069f0 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2183,6 +2183,18 @@ "frontend": true } }, + { + "metadata": { + "name": "prometheusRunQueriesInParallel", + "resourceVersion": "1720677541862", + "creationTimestamp": "2024-07-11T05:59:01Z" + }, + "spec": { + "description": "Enables running Prometheus queries in parallel", + "stage": "privatePreview", + "codeowner": "@grafana/observability-metrics" + } + }, { "metadata": { "name": "publicDashboards", From 6dce2ecbde887a5b8cd4f9d1a207e4363c7a1a9a Mon Sep 17 00:00:00 2001 From: Domas Date: Mon, 12 Aug 2024 15:36:52 +0300 Subject: [PATCH 023/229] Sparkline: Support spanNulls graph config prop (#91797) support spanNulls config prop for sparkline --- packages/grafana-ui/src/components/Sparkline/Sparkline.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx index 1ecba02b578..09d58a9ebac 100644 --- a/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx +++ b/packages/grafana-ui/src/components/Sparkline/Sparkline.tsx @@ -207,6 +207,7 @@ export class Sparkline extends PureComponent { fillColor: customConfig.fillColor, lineStyle: customConfig.lineStyle, gradientMode: customConfig.gradientMode, + spanNulls: customConfig.spanNulls, }); } From 379249fc601f71fcf7484d2455ac56af44d79cf6 Mon Sep 17 00:00:00 2001 From: Joey <90795735+joey-grafana@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:12:22 +0100 Subject: [PATCH 024/229] Tempo: Tidy up and organise (#90649) * Move test files to folder * Update paths * Tidy up --- .../SearchTraceQLEditor/GroupByField.test.tsx | 2 +- .../SearchTraceQLEditor/SearchField.test.tsx | 2 +- .../SearchTraceQLEditor/TagsInput.test.tsx | 2 +- .../TraceQLSearch.test.tsx | 2 +- .../tempo/VariableQueryEditor.test.tsx | 2 +- .../datasource/tempo/datasource.test.ts | 28 +-- .../plugins/datasource/tempo/datasource.ts | 202 +++++++++--------- .../datasource/tempo/graphTransform.test.ts | 2 +- .../tempo/resultTransformer.test.ts | 2 +- .../tempo/{ => test}/mockJsonResponse.json | 0 .../tempo/{ => test}/mockServiceGraph.json | 0 .../datasource/tempo/{ => test}/mocks.ts | 4 +- .../tempo/{ => test}/testResponse.ts | 0 .../datasource/tempo/{ => test}/test_utils.ts | 0 .../datasource/tempo/variables.test.ts | 2 +- 15 files changed, 124 insertions(+), 126 deletions(-) rename public/app/plugins/datasource/tempo/{ => test}/mockJsonResponse.json (100%) rename public/app/plugins/datasource/tempo/{ => test}/mockServiceGraph.json (100%) rename public/app/plugins/datasource/tempo/{ => test}/mocks.ts (94%) rename public/app/plugins/datasource/tempo/{ => test}/testResponse.ts (100%) rename public/app/plugins/datasource/tempo/{ => test}/test_utils.ts (100%) diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.test.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.test.tsx index 02e8b7190fc..b3a8dae79ae 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.test.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/GroupByField.test.tsx @@ -5,7 +5,7 @@ import { useState } from 'react'; import { TraceqlSearchScope } from '../dataquery.gen'; import { TempoDatasource } from '../datasource'; import TempoLanguageProvider from '../language_provider'; -import { initTemplateSrv } from '../test_utils'; +import { initTemplateSrv } from '../test/test_utils'; import { TempoQuery } from '../types'; import { GroupByField } from './GroupByField'; diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx index 55207d53415..7330956c63d 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/SearchField.test.tsx @@ -6,7 +6,7 @@ import { LanguageProvider } from '@grafana/data'; import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen'; import { TempoDatasource } from '../datasource'; import TempoLanguageProvider from '../language_provider'; -import { initTemplateSrv } from '../test_utils'; +import { initTemplateSrv } from '../test/test_utils'; import { keywordOperators, numberOperators, operators, stringOperators } from '../traceql/traceql'; import SearchField from './SearchField'; diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx index 6133badd652..b7834c57774 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TagsInput.test.tsx @@ -4,7 +4,7 @@ import userEvent from '@testing-library/user-event'; import { TraceqlFilter, TraceqlSearchScope } from '../dataquery.gen'; import { TempoDatasource } from '../datasource'; import TempoLanguageProvider from '../language_provider'; -import { initTemplateSrv } from '../test_utils'; +import { initTemplateSrv } from '../test/test_utils'; import { Scope } from '../types'; import TagsInput from './TagsInput'; diff --git a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx index 5bf430ba53e..ae2e26ed67f 100644 --- a/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx +++ b/public/app/plugins/datasource/tempo/SearchTraceQLEditor/TraceQLSearch.test.tsx @@ -7,7 +7,7 @@ import { config } from '@grafana/runtime'; import { TraceqlSearchScope } from '../dataquery.gen'; import { TempoDatasource } from '../datasource'; import TempoLanguageProvider from '../language_provider'; -import { initTemplateSrv } from '../test_utils'; +import { initTemplateSrv } from '../test/test_utils'; import { TempoQuery } from '../types'; import TraceQLSearch from './TraceQLSearch'; diff --git a/public/app/plugins/datasource/tempo/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/tempo/VariableQueryEditor.test.tsx index 700c2defc36..de0e63fb112 100644 --- a/public/app/plugins/datasource/tempo/VariableQueryEditor.test.tsx +++ b/public/app/plugins/datasource/tempo/VariableQueryEditor.test.tsx @@ -10,7 +10,7 @@ import { TempoVariableQueryType, } from './VariableQueryEditor'; import { selectOptionInTest } from './_importedDependencies/test/helpers/selectOptionInTest'; -import { createTempoDatasource } from './mocks'; +import { createTempoDatasource } from './test/mocks'; const refId = 'TempoDatasourceVariableQueryEditor-VariableQuery'; diff --git a/public/app/plugins/datasource/tempo/datasource.test.ts b/public/app/plugins/datasource/tempo/datasource.test.ts index 43dbb94b2a7..6d0cb41785e 100644 --- a/public/app/plugins/datasource/tempo/datasource.test.ts +++ b/public/app/plugins/datasource/tempo/datasource.test.ts @@ -41,10 +41,10 @@ import { getFieldConfig, getEscapedSpanNames, } from './datasource'; -import mockJson from './mockJsonResponse.json'; -import mockServiceGraph from './mockServiceGraph.json'; -import { createMetadataRequest, createTempoDatasource } from './mocks'; -import { initTemplateSrv } from './test_utils'; +import mockJson from './test/mockJsonResponse.json'; +import mockServiceGraph from './test/mockServiceGraph.json'; +import { createMetadataRequest, createTempoDatasource } from './test/mocks'; +import { initTemplateSrv } from './test/test_utils'; import { TempoJsonData, TempoQuery } from './types'; let mockObservable: () => Observable; @@ -68,7 +68,7 @@ describe('Tempo data source', () => { describe('runs correctly', () => { config.featureToggles.traceQLStreaming = true; jest.spyOn(TempoDatasource.prototype, 'isFeatureAvailable').mockImplementation(() => true); - const handleStreamingSearch = jest.spyOn(TempoDatasource.prototype, 'handleStreamingSearch'); + const handleStreamingQuery = jest.spyOn(TempoDatasource.prototype, 'handleStreamingQuery'); const request = jest.spyOn(TempoDatasource.prototype, '_request'); const templateSrv: TemplateSrv = { replace: (s: string) => s } as unknown as TemplateSrv; @@ -104,7 +104,7 @@ describe('Tempo data source', () => { config.liveEnabled = true; const ds = new TempoDatasource(defaultSettings, templateSrv); await lastValueFrom(ds.query(traceqlQuery as DataQueryRequest)); - expect(handleStreamingSearch).toHaveBeenCalledTimes(1); + expect(handleStreamingQuery).toHaveBeenCalledTimes(1); expect(request).toHaveBeenCalledTimes(0); }); @@ -112,7 +112,7 @@ describe('Tempo data source', () => { config.liveEnabled = true; const ds = new TempoDatasource(defaultSettings, templateSrv); await lastValueFrom(ds.query(traceqlSearchQuery as DataQueryRequest)); - expect(handleStreamingSearch).toHaveBeenCalledTimes(1); + expect(handleStreamingQuery).toHaveBeenCalledTimes(1); expect(request).toHaveBeenCalledTimes(0); }); @@ -120,7 +120,7 @@ describe('Tempo data source', () => { config.liveEnabled = false; const ds = new TempoDatasource(defaultSettings, templateSrv); await lastValueFrom(ds.query(traceqlQuery as DataQueryRequest)); - expect(handleStreamingSearch).toHaveBeenCalledTimes(1); + expect(handleStreamingQuery).toHaveBeenCalledTimes(1); expect(request).toHaveBeenCalledTimes(1); }); @@ -128,7 +128,7 @@ describe('Tempo data source', () => { config.liveEnabled = false; const ds = new TempoDatasource(defaultSettings, templateSrv); await lastValueFrom(ds.query(traceqlSearchQuery as DataQueryRequest)); - expect(handleStreamingSearch).toHaveBeenCalledTimes(1); + expect(handleStreamingQuery).toHaveBeenCalledTimes(1); expect(request).toHaveBeenCalledTimes(1); }); }); @@ -365,14 +365,14 @@ describe('Tempo data source', () => { describe('test the testDatasource function', () => { it('should return a success msg if response.ok is true', async () => { mockObservable = () => of({ ok: true }); - const handleStreamingSearch = jest - .spyOn(TempoDatasource.prototype, 'handleStreamingSearch') + const handleStreamingQuery = jest + .spyOn(TempoDatasource.prototype, 'handleStreamingQuery') .mockImplementation(() => of({ data: [] })); const ds = new TempoDatasource(defaultSettings); const response = await ds.testDatasource(); expect(response.status).toBe('success'); - expect(handleStreamingSearch).toHaveBeenCalled(); + expect(handleStreamingQuery).toHaveBeenCalled(); }); }); @@ -397,7 +397,7 @@ describe('Tempo data source', () => { raw: { from: 'now-15m', to: 'now' }, }; - const request = ds.traceIdQueryRequest( + const request = ds.makeTraceIdRequest( { requestId: 'test', interval: '', @@ -426,7 +426,7 @@ describe('Tempo data source', () => { jsonData: { traceQuery: { timeShiftEnabled: false, spanStartTimeShift: '2m', spanEndTimeShift: '4m' } }, }); - const request = ds.traceIdQueryRequest( + const request = ds.makeTraceIdRequest( { requestId: 'test', interval: '', diff --git a/public/app/plugins/datasource/tempo/datasource.ts b/public/app/plugins/datasource/tempo/datasource.ts index da625c8b8bc..44cd4f14169 100644 --- a/public/app/plugins/datasource/tempo/datasource.ts +++ b/public/app/plugins/datasource/tempo/datasource.ts @@ -283,6 +283,19 @@ export class TempoDatasource extends DataSourceWithBackend): Observable { const subQueries: Array> = []; const filteredTargets = options.targets.filter((target) => !target.hide); @@ -358,7 +371,7 @@ export class TempoDatasource extends DataSourceWithBackend) => { - reportInteraction('grafana_traces_metrics_summary_queried', { - datasourceType: 'tempo', - app: options.app ?? '', - grafana_version: config.buildInfo.version, - filterCount: target.groupBy?.length ?? 0, - }); - - if (query === '{}') { - return of({ - error: { - message: - 'Please ensure you do not have an empty query. This is so filters are applied and the metrics summary is not generated from all spans.', - }, - data: emptyResponse, - }); - } - - const groupBy = target.groupBy ? this.formatGroupBy(target.groupBy) : ''; - return this._request('/api/metrics/summary', { - q: query, - groupBy, - start: options.range.from.unix(), - end: options.range.to.unix(), - }).pipe( - map((response) => { - if (!response.data.summaries) { - return { - error: { - message: getErrorMessage(`No summary data for '${groupBy}'.`), - }, - data: emptyResponse, - }; - } - // Check if any of the results have series data as older versions of Tempo placed the series data in a different structure - const hasSeries = response.data.summaries.some((summary: MetricsSummary) => summary.series.length > 0); - if (!hasSeries) { - return { - error: { - message: getErrorMessage(`No series data. Ensure you are using an up to date version of Tempo`), - }, - data: emptyResponse, - }; - } - return { - data: createTableFrameFromMetricsSummaryQuery(response.data.summaries, query, this.instanceSettings), - }; - }), - catchError((error) => { - return of({ - error: { message: getErrorMessage(error.data.message) }, - data: emptyResponse, - }); - }) - ); - }; - formatGroupBy = (groupBy: TraceqlFilter[]) => { return groupBy ?.filter((f) => f.tag) @@ -604,9 +547,8 @@ export class TempoDatasource extends DataSourceWithBackend { if (response.error) { return response; @@ -624,7 +566,7 @@ export class TempoDatasource extends DataSourceWithBackend => { if (this.isStreamingSearchEnabled()) { - return this.handleStreamingSearch(options, targets.traceql, queryValue); + return this.handleStreamingQuery(options, targets.traceql, queryValue); } else { return this._request('/api/search', { q: queryValue, @@ -672,34 +614,66 @@ export class TempoDatasource extends DataSourceWithBackend, targets: TempoQuery[]): DataQueryRequest { - const request = { - ...options, - targets, - }; + handleMetricsSummaryQuery = (target: TempoQuery, query: string, options: DataQueryRequest) => { + reportInteraction('grafana_traces_metrics_summary_queried', { + datasourceType: 'tempo', + app: options.app ?? '', + grafana_version: config.buildInfo.version, + filterCount: target.groupBy?.length ?? 0, + }); - if (this.traceQuery?.timeShiftEnabled) { - request.range = options.range && { - ...options.range, - from: dateTime(options.range.from).subtract( - rangeUtil.intervalToMs(this.traceQuery?.spanStartTimeShift || '30m'), - 'milliseconds' - ), - to: dateTime(options.range.to).add( - rangeUtil.intervalToMs(this.traceQuery?.spanEndTimeShift || '30m'), - 'milliseconds' - ), - }; - } else { - request.range = { from: dateTime(0), to: dateTime(0), raw: { from: dateTime(0), to: dateTime(0) } }; + if (query === '{}') { + return of({ + error: { + message: + 'Please ensure you do not have an empty query. This is so filters are applied and the metrics summary is not generated from all spans.', + }, + data: emptyResponse, + }); } - return request; - } + const groupBy = target.groupBy ? this.formatGroupBy(target.groupBy) : ''; + return this._request('/api/metrics/summary', { + q: query, + groupBy, + start: options.range.from.unix(), + end: options.range.to.unix(), + }).pipe( + map((response) => { + if (!response.data.summaries) { + return { + error: { + message: getErrorMessage(`No summary data for '${groupBy}'.`), + }, + data: emptyResponse, + }; + } + // Check if any of the results have series data as older versions of Tempo placed the series data in a different structure + const hasSeries = response.data.summaries.some((summary: MetricsSummary) => summary.series.length > 0); + if (!hasSeries) { + return { + error: { + message: getErrorMessage(`No series data. Ensure you are using an up to date version of Tempo`), + }, + data: emptyResponse, + }; + } + return { + data: createTableFrameFromMetricsSummaryQuery(response.data.summaries, query, this.instanceSettings), + }; + }), + catchError((error) => { + return of({ + error: { message: getErrorMessage(error.data.message) }, + data: emptyResponse, + }); + }) + ); + }; // This function can probably be simplified by avoiding passing both `targets` and `query`, // since `query` is built from `targets`, if you look at how this function is currently called - handleStreamingSearch( + handleStreamingQuery( options: DataQueryRequest, targets: TempoQuery[], query: string @@ -720,6 +694,31 @@ export class TempoDatasource extends DataSourceWithBackend, targets: TempoQuery[]): DataQueryRequest { + const request = { + ...options, + targets, + }; + + if (this.traceQuery?.timeShiftEnabled) { + request.range = options.range && { + ...options.range, + from: dateTime(options.range.from).subtract( + rangeUtil.intervalToMs(this.traceQuery?.spanStartTimeShift || '30m'), + 'milliseconds' + ), + to: dateTime(options.range.to).add( + rangeUtil.intervalToMs(this.traceQuery?.spanEndTimeShift || '30m'), + 'milliseconds' + ), + }; + } else { + request.range = { from: dateTime(0), to: dateTime(0), raw: { from: dateTime(0), to: dateTime(0) } }; + } + + return request; + } + async metadataRequest(url: string, params = {}) { return await lastValueFrom(this._request(url, params, { method: 'GET', hideFromInspector: true })); } @@ -728,7 +727,6 @@ export class TempoDatasource extends DataSourceWithBackend): DataQ }; } -function getServiceGraphView( +function getServiceGraphViewDataFrames( request: DataQueryRequest, rateResponse: ServiceMapQueryResponseWithRates, secondResponse: DataQueryResponse, diff --git a/public/app/plugins/datasource/tempo/graphTransform.test.ts b/public/app/plugins/datasource/tempo/graphTransform.test.ts index efebee60cd6..7a5ab7ccac8 100644 --- a/public/app/plugins/datasource/tempo/graphTransform.test.ts +++ b/public/app/plugins/datasource/tempo/graphTransform.test.ts @@ -1,7 +1,7 @@ import { DataFrameView, dateTime, createDataFrame, FieldType } from '@grafana/data'; import { createGraphFrames, mapPromMetricsToServiceMap } from './graphTransform'; -import { bigResponse } from './testResponse'; +import { bigResponse } from './test/testResponse'; describe('createGraphFrames', () => { it('transforms basic response into nodes and edges frame', async () => { diff --git a/public/app/plugins/datasource/tempo/resultTransformer.test.ts b/public/app/plugins/datasource/tempo/resultTransformer.test.ts index 0c9c4750755..fc47f88dab2 100644 --- a/public/app/plugins/datasource/tempo/resultTransformer.test.ts +++ b/public/app/plugins/datasource/tempo/resultTransformer.test.ts @@ -14,7 +14,7 @@ import { otlpDataFrameFromResponse, otlpResponse, traceQlResponse, -} from './testResponse'; +} from './test/testResponse'; import { TraceSearchMetadata } from './types'; const defaultSettings: DataSourceInstanceSettings = { diff --git a/public/app/plugins/datasource/tempo/mockJsonResponse.json b/public/app/plugins/datasource/tempo/test/mockJsonResponse.json similarity index 100% rename from public/app/plugins/datasource/tempo/mockJsonResponse.json rename to public/app/plugins/datasource/tempo/test/mockJsonResponse.json diff --git a/public/app/plugins/datasource/tempo/mockServiceGraph.json b/public/app/plugins/datasource/tempo/test/mockServiceGraph.json similarity index 100% rename from public/app/plugins/datasource/tempo/mockServiceGraph.json rename to public/app/plugins/datasource/tempo/test/mockServiceGraph.json diff --git a/public/app/plugins/datasource/tempo/mocks.ts b/public/app/plugins/datasource/tempo/test/mocks.ts similarity index 94% rename from public/app/plugins/datasource/tempo/mocks.ts rename to public/app/plugins/datasource/tempo/test/mocks.ts index 8a9c568ac81..28fe7c66f91 100644 --- a/public/app/plugins/datasource/tempo/mocks.ts +++ b/public/app/plugins/datasource/tempo/test/mocks.ts @@ -1,8 +1,8 @@ import { DataSourceInstanceSettings, PluginType, toUtc } from '@grafana/data'; import { TemplateSrv } from '@grafana/runtime'; -import { TempoDatasource } from './datasource'; -import { TempoJsonData } from './types'; +import { TempoDatasource } from '../datasource'; +import { TempoJsonData } from '../types'; const rawRange = { from: toUtc('2018-04-25 10:00'), diff --git a/public/app/plugins/datasource/tempo/testResponse.ts b/public/app/plugins/datasource/tempo/test/testResponse.ts similarity index 100% rename from public/app/plugins/datasource/tempo/testResponse.ts rename to public/app/plugins/datasource/tempo/test/testResponse.ts diff --git a/public/app/plugins/datasource/tempo/test_utils.ts b/public/app/plugins/datasource/tempo/test/test_utils.ts similarity index 100% rename from public/app/plugins/datasource/tempo/test_utils.ts rename to public/app/plugins/datasource/tempo/test/test_utils.ts diff --git a/public/app/plugins/datasource/tempo/variables.test.ts b/public/app/plugins/datasource/tempo/variables.test.ts index 1b5f9fce94c..4a2215c4aae 100644 --- a/public/app/plugins/datasource/tempo/variables.test.ts +++ b/public/app/plugins/datasource/tempo/variables.test.ts @@ -3,7 +3,7 @@ import { lastValueFrom } from 'rxjs'; import { DataQueryRequest, TimeRange } from '@grafana/data'; import { TempoVariableQuery } from './VariableQueryEditor'; -import { createMetadataRequest, createTempoDatasource } from './mocks'; +import { createMetadataRequest, createTempoDatasource } from './test/mocks'; import { TempoVariableSupport } from './variables'; describe('TempoVariableSupport', () => { From d00b4879d24dce8aa1f1d38259047d50ea327e10 Mon Sep 17 00:00:00 2001 From: Joey <90795735+joey-grafana@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:15:06 +0100 Subject: [PATCH 025/229] Tempo: Simplify span filters logic and fix regex for tag only search (#90819) Simplify span filters logic and fix regex for tag only search --- .../components/utils/filter-spans.tsx | 44 ++++++------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx index 44845220f38..942a13a476b 100644 --- a/public/app/features/explore/TraceView/components/utils/filter-spans.tsx +++ b/public/app/features/explore/TraceView/components/utils/filter-spans.tsx @@ -109,17 +109,15 @@ const getTagMatches = (spans: TraceSpan[], tags: Tag[]) => { // match against every tag filter return tags.every((tag: Tag) => { if (tag.key && tag.value) { - if (tag.operator === '=' && checkKeyValConditionForMatch(tag, span)) { - return getReturnValue(tag.operator, true); - } else if (tag.operator === '=~' && checkKeyValConditionForRegex(tag, span)) { - return getReturnValue(tag.operator, false); - } else if (tag.operator === '!=' && !checkKeyValConditionForMatch(tag, span)) { - return getReturnValue(tag.operator, false); - } else if (tag.operator === '!~' && !checkKeyValConditionForRegex(tag, span)) { - return getReturnValue(tag.operator, false); - } else { - return false; + if ( + (tag.operator === '=' && checkKeyValConditionForMatch(tag, span)) || + (tag.operator === '=~' && checkKeyValConditionForRegex(tag, span)) || + (tag.operator === '!=' && !checkKeyValConditionForMatch(tag, span)) || + (tag.operator === '!~' && !checkKeyValConditionForRegex(tag, span)) + ) { + return true; } + return false; } else if (tag.key) { if ( span.tags.some((kv) => checkKeyForMatch(tag.key!, kv.key)) || @@ -133,10 +131,11 @@ const getTagMatches = (spans: TraceSpan[], tags: Tag[]) => { (span.traceState && tag.key === TRACE_STATE) || tag.key === ID ) { - return getReturnValue(tag.operator, true); + return tag.operator === '=' || tag.operator === '=~' ? true : false; } + return tag.operator === '=' || tag.operator === '=~' ? false : true; } - return getReturnValue(tag.operator, false); + return false; }); }); } @@ -184,30 +183,15 @@ const checkKeyValConditionForMatch = (tag: Tag, span: TraceSpan) => { }; const checkKeyForMatch = (tagKey: string, key: string) => { - return tagKey === key.toString() ? true : false; + return tagKey === key.toString(); }; const checkKeyAndValueForMatch = (tag: Tag, kv: TraceKeyValuePair) => { - return tag.key === kv.key.toString() && tag.value === kv.value.toString() ? true : false; + return tag.key === kv.key.toString() && tag.value === kv.value.toString(); }; const checkKeyAndValueForRegex = (tag: Tag, kv: TraceKeyValuePair) => { - return kv.key.toString().includes(tag.key || '') && kv.value.toString().includes(tag.value || '') ? true : false; -}; - -const getReturnValue = (operator: string, found: boolean) => { - switch (operator) { - case '=': - return found; - case '!=': - return !found; - case '~=': - return !found; - case '!~': - return !found; - default: - return !found; - } + return kv.key.toString().includes(tag.key || '') && kv.value.toString().includes(tag.value || ''); }; const getServiceNameMatches = (spans: TraceSpan[], searchProps: SearchProps) => { From b9cece8f9eb66df6f9405ab721b923fde315b6ee Mon Sep 17 00:00:00 2001 From: Joao Silva <100691367+JoaoSilvaGrafana@users.noreply.github.com> Date: Mon, 12 Aug 2024 14:31:01 +0100 Subject: [PATCH 026/229] Bookmarks: Create Bookmarks landing page (#91538) --- .../components/AppChrome/MegaMenu/utils.ts | 14 ++++++ .../components/Bookmarks/BookmarksPage.tsx | 49 +++++++++++++++++++ public/app/routes/routes.tsx | 3 +- 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 public/app/core/components/Bookmarks/BookmarksPage.tsx diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.ts b/public/app/core/components/AppChrome/MegaMenu/utils.ts index b83d420aa94..ab41e68dee6 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.ts @@ -130,3 +130,17 @@ export function getEditionAndUpdateLinks(): NavModelItem[] { return links; } + +export function findByUrl(nodes: NavModelItem[], url: string): NavModelItem | null { + for (const item of nodes) { + if (item.url === url) { + return item; + } else if (item.children?.length) { + const found = findByUrl(item.children, url); + if (found) { + return found; + } + } + } + return null; +} diff --git a/public/app/core/components/Bookmarks/BookmarksPage.tsx b/public/app/core/components/Bookmarks/BookmarksPage.tsx new file mode 100644 index 00000000000..8674b7a11c9 --- /dev/null +++ b/public/app/core/components/Bookmarks/BookmarksPage.tsx @@ -0,0 +1,49 @@ +import { css } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; +import { Page } from 'app/core/components/Page/Page'; +import { useSelector } from 'app/types'; + +import { usePinnedItems } from '../AppChrome/MegaMenu/hooks'; +import { findByUrl } from '../AppChrome/MegaMenu/utils'; +import { NavLandingPageCard } from '../NavLandingPage/NavLandingPageCard'; + +export function BookmarksPage() { + const styles = useStyles2(getStyles); + const pinnedItems = usePinnedItems(); + const navTree = useSelector((state) => state.navBarTree); + + return ( + + +
+ {pinnedItems.map((url) => { + const item = findByUrl(navTree, url); + if (!item) { + return null; + } + return ( + + ); + })} +
+
+
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + grid: css({ + display: 'grid', + gap: theme.spacing(3), + gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', + gridAutoRows: '138px', + padding: theme.spacing(2, 0), + }), +}); diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index 552a3431479..a52aafa5eeb 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -20,6 +20,7 @@ import { getAppPluginRoutes } from 'app/features/plugins/routes'; import { getProfileRoutes } from 'app/features/profile/routes'; import { AccessControlAction, DashboardRoutes } from 'app/types'; +import { BookmarksPage } from '../core/components/Bookmarks/BookmarksPage'; import { SafeDynamicImport } from '../core/components/DynamicImports/SafeDynamicImport'; import { RouteDescriptor } from '../core/navigation/types'; import { getPublicDashboardRoutes } from '../features/dashboard/routes'; @@ -513,7 +514,7 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/bookmarks', - component: () => , + component: () => , }, ...getPluginCatalogRoutes(), ...getSupportBundleRoutes(), From 2ed6ca360fcaf83ab2eb50f85240a19cd0ebc64d Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Mon, 12 Aug 2024 15:43:42 +0200 Subject: [PATCH 027/229] Extensions: e2e test usePluginComponent hook (#91750) * add simple test apps that use usePluginComponent hook and exposeComponent api * add e2e test * update readme * Update README.md * fix lint issue * pr feedback --- devenv/plugins.yaml | 8 +++++ .../app-with-exposed-components/README.md | 22 ++++++++++++ .../app-with-exposed-components/img/logo.svg | 1 + .../app-with-exposed-components/module.js | 28 +++++++++++++++ .../app-with-exposed-components/plugin.json | 35 +++++++++++++++++++ .../myorg-componentexposer-app/img/logo.svg | 1 + .../myorg-componentexposer-app/module.js | 14 ++++++++ .../myorg-componentexposer-app/plugin.json | 35 +++++++++++++++++++ .../app-with-extension-point/plugin.json | 4 +-- .../plugins/myorg-b-app/plugin.json | 18 +++++----- .../{ => extensions}/extensionPoints.spec.ts | 0 .../{ => extensions}/extensions.spec.ts | 0 .../extensions/useExposedComponent.spec.ts | 9 +++++ 13 files changed, 162 insertions(+), 13 deletions(-) create mode 100644 e2e/custom-plugins/app-with-exposed-components/README.md create mode 100644 e2e/custom-plugins/app-with-exposed-components/img/logo.svg create mode 100644 e2e/custom-plugins/app-with-exposed-components/module.js create mode 100644 e2e/custom-plugins/app-with-exposed-components/plugin.json create mode 100644 e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/img/logo.svg create mode 100644 e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/module.js create mode 100644 e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/plugin.json rename e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/{ => extensions}/extensionPoints.spec.ts (100%) rename e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/{ => extensions}/extensions.spec.ts (100%) create mode 100644 e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions/useExposedComponent.spec.ts diff --git a/devenv/plugins.yaml b/devenv/plugins.yaml index 9e488cc065f..097428a6ca9 100644 --- a/devenv/plugins.yaml +++ b/devenv/plugins.yaml @@ -17,3 +17,11 @@ apps: org_id: 1 org_name: Main Org. disabled: false + - type: myorg-componentconsumer-app + org_id: 1 + org_name: Main Org. + disabled: false + - type: myorg-componentexposer-app + org_id: 1 + org_name: Main Org. + disabled: false diff --git a/e2e/custom-plugins/app-with-exposed-components/README.md b/e2e/custom-plugins/app-with-exposed-components/README.md new file mode 100644 index 00000000000..03590601496 --- /dev/null +++ b/e2e/custom-plugins/app-with-exposed-components/README.md @@ -0,0 +1,22 @@ +# App with exposed components + +This directory contains two apps - `myorg-componentconsumer-app` and `myorg-componentexposer-app` which is nested inside `myorg-componentconsumer-app`. + +`myorg-componentconsumer-app` exposes a simple React component using the [`exposeComponent`](https://grafana.com/developers/plugin-tools/reference/ui-extensions#exposecomponent) api. `myorg-componentconsumer-app` in turn, consumes this compoment using the [`https://grafana.com/developers/plugin-tools/reference/ui-extensions#useplugincomponent`](https://grafana.com/developers/plugin-tools/reference/ui-extensions#useplugincomponent) hook. + +To test this app: + +```sh +# start e2e test instance (it will install this plugin) +PORT=3000 ./scripts/grafana-server/start-server +# run Playwright tests using Playwright VSCode extension or with the following script +yarn e2e:playwright +``` + +or + +``` +PORT=3000 ./scripts/grafana-server/start-server +yarn start +yarn e2e +``` diff --git a/e2e/custom-plugins/app-with-exposed-components/img/logo.svg b/e2e/custom-plugins/app-with-exposed-components/img/logo.svg new file mode 100644 index 00000000000..3d284dea3af --- /dev/null +++ b/e2e/custom-plugins/app-with-exposed-components/img/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/custom-plugins/app-with-exposed-components/module.js b/e2e/custom-plugins/app-with-exposed-components/module.js new file mode 100644 index 00000000000..b73fbd617ce --- /dev/null +++ b/e2e/custom-plugins/app-with-exposed-components/module.js @@ -0,0 +1,28 @@ +define(['@grafana/data', '@grafana/runtime', 'react'], function (grafanaData, grafanaRuntime, React) { + var AppPlugin = grafanaData.AppPlugin; + var usePluginComponent = grafanaRuntime.usePluginComponent; + + var MyComponent = function () { + var plugin = usePluginComponent('myorg-componentexposer-app/reusable-component/v1'); + var TestComponent = plugin.component; + var isLoading = plugin.isLoading; + + if (!TestComponent) { + return null; + } + + return React.createElement( + React.Fragment, + null, + React.createElement('div', null, 'Exposed component:'), + isLoading ? 'Loading..' : React.createElement(TestComponent, { name: 'World' }) + ); + }; + + var App = function () { + return React.createElement('div', null, 'Hello Grafana!', React.createElement(MyComponent, null)); + }; + + var plugin = new AppPlugin().setRootPage(App); + return { plugin: plugin }; +}); diff --git a/e2e/custom-plugins/app-with-exposed-components/plugin.json b/e2e/custom-plugins/app-with-exposed-components/plugin.json new file mode 100644 index 00000000000..caf74b2d1a8 --- /dev/null +++ b/e2e/custom-plugins/app-with-exposed-components/plugin.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://raw.githubusercontent.com/grafana/grafana/main/docs/sources/developers/plugins/plugin.schema.json", + "type": "app", + "name": "Extensions exposed component App", + "id": "myorg-componentconsumer-app", + "preload": true, + "info": { + "keywords": ["app"], + "description": "Example on how to extend grafana ui from a plugin", + "author": { + "name": "Myorg" + }, + "logos": { + "small": "img/logo.svg", + "large": "img/logo.svg" + }, + "screenshots": [], + "version": "1.0.0", + "updated": "2024-08-09" + }, + "includes": [ + { + "type": "page", + "name": "Default", + "path": "/a/myorg-componentconsumer-app", + "role": "Admin", + "addToNav": true, + "defaultNav": true + } + ], + "dependencies": { + "grafanaDependency": ">=10.3.3", + "plugins": [] + } +} diff --git a/e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/img/logo.svg b/e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/img/logo.svg new file mode 100644 index 00000000000..3d284dea3af --- /dev/null +++ b/e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/img/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/module.js b/e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/module.js new file mode 100644 index 00000000000..4ccaba8ca36 --- /dev/null +++ b/e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/module.js @@ -0,0 +1,14 @@ +define(['@grafana/data', 'module', 'react'], function (grafanaData, amdModule, React) { + const plugin = new grafanaData.AppPlugin().exposeComponent({ + id: 'myorg-componentexposer-app/reusable-component/v1', + title: 'Reusable component', + description: 'A component that can be reused by other app plugins.', + component: function ({ name }) { + return React.createElement('div', { 'data-testid': 'exposed-component' }, 'Hello ', name, '!'); + }, + }); + + return { + plugin: plugin, + }; +}); diff --git a/e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/plugin.json b/e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/plugin.json new file mode 100644 index 00000000000..3487cd45540 --- /dev/null +++ b/e2e/custom-plugins/app-with-exposed-components/plugins/myorg-componentexposer-app/plugin.json @@ -0,0 +1,35 @@ +{ + "$schema": "https://raw.githubusercontent.com/grafana/grafana/main/docs/sources/developers/plugins/plugin.schema.json", + "type": "app", + "name": "A App", + "id": "myorg-componentexposer-app", + "preload": true, + "info": { + "keywords": ["app"], + "description": "Will extend root app with ui extensions", + "author": { + "name": "Myorg" + }, + "logos": { + "small": "img/logo.svg", + "large": "img/logo.svg" + }, + "screenshots": [], + "version": "%VERSION%", + "updated": "%TODAY%" + }, + "includes": [ + { + "type": "page", + "name": "Default", + "path": "/a/myorg-componentexposer-app", + "role": "Admin", + "addToNav": false, + "defaultNav": false + } + ], + "dependencies": { + "grafanaDependency": ">=10.3.3", + "plugins": [] + } +} diff --git a/e2e/custom-plugins/app-with-extension-point/plugin.json b/e2e/custom-plugins/app-with-extension-point/plugin.json index b1d3c396384..3a4ab08faa2 100644 --- a/e2e/custom-plugins/app-with-extension-point/plugin.json +++ b/e2e/custom-plugins/app-with-extension-point/plugin.json @@ -32,7 +32,5 @@ "grafanaDependency": ">=10.3.3", "plugins": [] }, - "generated": { - "extensions": [] - } + "extensions": [] } diff --git a/e2e/custom-plugins/app-with-extension-point/plugins/myorg-b-app/plugin.json b/e2e/custom-plugins/app-with-extension-point/plugins/myorg-b-app/plugin.json index ac501bc7f56..7bd55c0e572 100644 --- a/e2e/custom-plugins/app-with-extension-point/plugins/myorg-b-app/plugin.json +++ b/e2e/custom-plugins/app-with-extension-point/plugins/myorg-b-app/plugin.json @@ -32,14 +32,12 @@ "grafanaDependency": ">=10.3.3", "plugins": [] }, - "generated": { - "extensions": [ - { - "extensionPointId": "plugins/myorg-extensionpoint-app/actions", - "title": "Open from B", - "description": "Open a modal from plugin B", - "type": "link" - } - ] - } + "extensions": [ + { + "extensionPointId": "plugins/myorg-extensionpoint-app/actions", + "title": "Open from B", + "description": "Open a modal from plugin B", + "type": "link" + } + ] } diff --git a/e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensionPoints.spec.ts b/e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions/extensionPoints.spec.ts similarity index 100% rename from e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensionPoints.spec.ts rename to e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions/extensionPoints.spec.ts diff --git a/e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions.spec.ts b/e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions/extensions.spec.ts similarity index 100% rename from e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions.spec.ts rename to e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions/extensions.spec.ts diff --git a/e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions/useExposedComponent.spec.ts b/e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions/useExposedComponent.spec.ts new file mode 100644 index 00000000000..9a01139b445 --- /dev/null +++ b/e2e/plugin-e2e/plugin-e2e-api-tests/as-admin-user/extensions/useExposedComponent.spec.ts @@ -0,0 +1,9 @@ +import { test, expect } from '@grafana/plugin-e2e'; + +const pluginId = 'myorg-componentconsumer-app'; +const exposedComponentTestId = 'exposed-component'; + +test('should display component exposed by another app', async ({ page }) => { + await page.goto(`/a/${pluginId}`); + await expect(await page.getByTestId(exposedComponentTestId)).toHaveText('Hello World!'); +}); From 25dbb32cea0ddc77487e7a6247e06a326b62b927 Mon Sep 17 00:00:00 2001 From: Fayzal Ghantiwala <114010985+fayzal-g@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:37:15 +0100 Subject: [PATCH 028/229] Alerting: Vendor in latest grafana/alerting package (#91786) * temp * vendor * Remove dead code * Vendoring --- go.mod | 2 +- go.sum | 4 +- pkg/services/ngalert/api/api_alertmanager.go | 56 ++------------- .../ngalert/api/api_alertmanager_test.go | 68 ------------------- .../alertmanager_mock/Alertmanager.go | 35 ++++++---- .../notifier/legacy_storage/persist_mock.go | 10 ++- .../ngalert/notifier/multiorg_alertmanager.go | 2 +- .../ngalert/notifier/testreceivers.go | 52 +------------- pkg/services/ngalert/remote/alertmanager.go | 4 +- .../remote/forked_alertmanager_test.go | 16 ++--- .../ngalert/remote/mock/remoteAlertmanager.go | 35 ++++++---- .../remote_primary_forked_alertmanager.go | 2 +- .../remote_secondary_forked_alertmanager.go | 2 +- 13 files changed, 72 insertions(+), 216 deletions(-) diff --git a/go.mod b/go.mod index 8bf438d9e44..3869249170e 100644 --- a/go.mod +++ b/go.mod @@ -74,7 +74,7 @@ require ( github.com/googleapis/gax-go/v2 v2.12.3 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20240723124849-f2ab7c7b8f7d // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20240812131556-611a23ff0f7f // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20240812070441-ccb639ea96d0 // @grafana/identity-access-team github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 // @grafana/identity-access-team github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad diff --git a/go.sum b/go.sum index 4bfd3f335b3..73da8d916a2 100644 --- a/go.sum +++ b/go.sum @@ -2309,8 +2309,8 @@ github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/ad github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20240723124849-f2ab7c7b8f7d h1:d2NZeTs+zBPVMd8uOOV5+6lyfs0BCDKxtiNxIMjnPNA= -github.com/grafana/alerting v0.0.0-20240723124849-f2ab7c7b8f7d/go.mod h1:DLj8frbtCaITljC2jc0L85JQViPF3mPfOSiYhm1osso= +github.com/grafana/alerting v0.0.0-20240812131556-611a23ff0f7f h1:c8QAFXkilBiF29xc7oKO2IkbGE3bp9NIKgiNLazdooY= +github.com/grafana/alerting v0.0.0-20240812131556-611a23ff0f7f/go.mod h1:DLj8frbtCaITljC2jc0L85JQViPF3mPfOSiYhm1osso= github.com/grafana/authlib v0.0.0-20240812070441-ccb639ea96d0 h1:LDLHuN0nwa9fwZUKQrOBflePLxzOz4u4AuNutI78AHk= github.com/grafana/authlib v0.0.0-20240812070441-ccb639ea96d0/go.mod h1:71+xJm0AE6eNGNExUvnABtyEztQ/Acb53/TAdOgwdmc= github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 h1:uD1LcKwvEAqzDsgVChBudPqo5BhPxkj9AgylT5QCReo= diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index 41205be0897..4d9f3bec5a5 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -254,7 +254,7 @@ func (srv AlertmanagerSrv) RoutePostTestReceivers(c *contextmodel.ReqContext, bo return errResp } - result, err := am.TestReceivers(ctx, body) + result, status, err := am.TestReceivers(ctx, body) if err != nil { if errors.Is(err, alertingNotify.ErrNoReceivers) { return response.Error(http.StatusBadRequest, "", err) @@ -262,7 +262,7 @@ func (srv AlertmanagerSrv) RoutePostTestReceivers(c *contextmodel.ReqContext, bo return response.Error(http.StatusInternalServerError, "", err) } - return response.JSON(statusForTestReceivers(result.Receivers), newTestReceiversResult(result)) + return response.JSON(status, newTestReceiversResult(result)) } func (srv AlertmanagerSrv) RoutePostTestTemplates(c *contextmodel.ReqContext, body apimodels.TestTemplatesConfigBodyParams) response.Response { @@ -301,7 +301,7 @@ func contextWithTimeoutFromRequest(ctx context.Context, r *http.Request, default return ctx, cancelFunc, nil } -func newTestReceiversResult(r *notifier.TestReceiversResult) apimodels.TestReceiversResult { +func newTestReceiversResult(r *alertingNotify.TestReceiversResult) apimodels.TestReceiversResult { v := apimodels.TestReceiversResult{ Alert: apimodels.TestReceiversConfigAlertParams{ Annotations: r.Alert.Annotations, @@ -316,9 +316,7 @@ func newTestReceiversResult(r *notifier.TestReceiversResult) apimodels.TestRecei configs[jx].Name = config.Name configs[jx].UID = config.UID configs[jx].Status = config.Status - if config.Error != nil { - configs[jx].Error = config.Error.Error() - } + configs[jx].Error = config.Error } v.Receivers[ix].Configs = configs v.Receivers[ix].Name = next.Name @@ -326,50 +324,6 @@ func newTestReceiversResult(r *notifier.TestReceiversResult) apimodels.TestRecei return v } -// statusForTestReceivers returns the appropriate status code for the response -// for the results. -// -// It returns an HTTP 200 OK status code if notifications were sent to all receivers, -// an HTTP 400 Bad Request status code if all receivers contain invalid configuration, -// an HTTP 408 Request Timeout status code if all receivers timed out when sending -// a test notification or an HTTP 207 Multi Status. -func statusForTestReceivers(v []notifier.TestReceiverResult) int { - var ( - numBadRequests int - numTimeouts int - numUnknownErrors int - ) - for _, receiver := range v { - for _, next := range receiver.Configs { - if next.Error != nil { - var ( - invalidReceiverErr alertingNotify.IntegrationValidationError - receiverTimeoutErr alertingNotify.IntegrationTimeoutError - ) - if errors.As(next.Error, &invalidReceiverErr) { - numBadRequests += 1 - } else if errors.As(next.Error, &receiverTimeoutErr) { - numTimeouts += 1 - } else { - numUnknownErrors += 1 - } - } - } - } - if numBadRequests == len(v) { - // if all receivers contain invalid configuration - return http.StatusBadRequest - } else if numTimeouts == len(v) { - // if all receivers contain valid configuration but timed out - return http.StatusRequestTimeout - } else if numBadRequests+numTimeouts+numUnknownErrors > 0 { - return http.StatusMultiStatus - } else { - // all receivers were sent a notification without error - return http.StatusOK - } -} - func newTestTemplateResult(res *notifier.TestTemplatesResults) apimodels.TestTemplatesResults { apiRes := apimodels.TestTemplatesResults{} for _, r := range res.Results { @@ -382,7 +336,7 @@ func newTestTemplateResult(res *notifier.TestTemplatesResults) apimodels.TestTem apiRes.Errors = append(apiRes.Errors, apimodels.TestTemplatesErrorResult{ Name: e.Name, Kind: apimodels.TemplateErrorKind(e.Kind), - Message: e.Error.Error(), + Message: e.Error, }) } return apiRes diff --git a/pkg/services/ngalert/api/api_alertmanager_test.go b/pkg/services/ngalert/api/api_alertmanager_test.go index d025bd86796..14676789294 100644 --- a/pkg/services/ngalert/api/api_alertmanager_test.go +++ b/pkg/services/ngalert/api/api_alertmanager_test.go @@ -14,8 +14,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" - alertingNotify "github.com/grafana/alerting/notify" - "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol" @@ -101,72 +99,6 @@ func TestContextWithTimeoutFromRequest(t *testing.T) { }) } -func TestStatusForTestReceivers(t *testing.T) { - t.Run("assert HTTP 400 Status Bad Request for no receivers", func(t *testing.T) { - require.Equal(t, http.StatusBadRequest, statusForTestReceivers([]notifier.TestReceiverResult{})) - }) - - t.Run("assert HTTP 400 Bad Request when all invalid receivers", func(t *testing.T) { - require.Equal(t, http.StatusBadRequest, statusForTestReceivers([]notifier.TestReceiverResult{{ - Name: "test1", - Configs: []notifier.TestReceiverConfigResult{{ - Name: "test1", - UID: "uid1", - Status: "failed", - Error: alertingNotify.IntegrationValidationError{}, - }}, - }, { - Name: "test2", - Configs: []notifier.TestReceiverConfigResult{{ - Name: "test2", - UID: "uid2", - Status: "failed", - Error: alertingNotify.IntegrationValidationError{}, - }}, - }})) - }) - - t.Run("assert HTTP 408 Request Timeout when all receivers timed out", func(t *testing.T) { - require.Equal(t, http.StatusRequestTimeout, statusForTestReceivers([]notifier.TestReceiverResult{{ - Name: "test1", - Configs: []notifier.TestReceiverConfigResult{{ - Name: "test1", - UID: "uid1", - Status: "failed", - Error: alertingNotify.IntegrationTimeoutError{}, - }}, - }, { - Name: "test2", - Configs: []notifier.TestReceiverConfigResult{{ - Name: "test2", - UID: "uid2", - Status: "failed", - Error: alertingNotify.IntegrationTimeoutError{}, - }}, - }})) - }) - - t.Run("assert 207 Multi Status for different errors", func(t *testing.T) { - require.Equal(t, http.StatusMultiStatus, statusForTestReceivers([]notifier.TestReceiverResult{{ - Name: "test1", - Configs: []notifier.TestReceiverConfigResult{{ - Name: "test1", - UID: "uid1", - Status: "failed", - Error: alertingNotify.IntegrationValidationError{}, - }}, - }, { - Name: "test2", - Configs: []notifier.TestReceiverConfigResult{{ - Name: "test2", - UID: "uid2", - Status: "failed", - Error: alertingNotify.IntegrationTimeoutError{}, - }}, - }})) - }) -} - func TestAlertmanagerConfig(t *testing.T) { sut := createSut(t) diff --git a/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go b/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go index b0b647233c1..a3419fdc508 100644 --- a/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go +++ b/pkg/services/ngalert/notifier/alertmanager_mock/Alertmanager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package alertmanager_mock @@ -13,8 +13,6 @@ import ( models "github.com/grafana/grafana/pkg/services/ngalert/models" - notifier "github.com/grafana/grafana/pkg/services/ngalert/notifier" - notify "github.com/grafana/alerting/notify" v2models "github.com/prometheus/alertmanager/api/v2/models" @@ -816,33 +814,40 @@ func (_c *AlertmanagerMock_StopAndWait_Call) RunAndReturn(run func()) *Alertmana } // TestReceivers provides a mock function with given fields: ctx, c -func (_m *AlertmanagerMock) TestReceivers(ctx context.Context, c definitions.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error) { +func (_m *AlertmanagerMock) TestReceivers(ctx context.Context, c definitions.TestReceiversConfigBodyParams) (*notify.TestReceiversResult, int, error) { ret := _m.Called(ctx, c) if len(ret) == 0 { panic("no return value specified for TestReceivers") } - var r0 *notifier.TestReceiversResult - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, definitions.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error)); ok { + var r0 *notify.TestReceiversResult + var r1 int + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, definitions.TestReceiversConfigBodyParams) (*notify.TestReceiversResult, int, error)); ok { return rf(ctx, c) } - if rf, ok := ret.Get(0).(func(context.Context, definitions.TestReceiversConfigBodyParams) *notifier.TestReceiversResult); ok { + if rf, ok := ret.Get(0).(func(context.Context, definitions.TestReceiversConfigBodyParams) *notify.TestReceiversResult); ok { r0 = rf(ctx, c) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*notifier.TestReceiversResult) + r0 = ret.Get(0).(*notify.TestReceiversResult) } } - if rf, ok := ret.Get(1).(func(context.Context, definitions.TestReceiversConfigBodyParams) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, definitions.TestReceiversConfigBodyParams) int); ok { r1 = rf(ctx, c) } else { - r1 = ret.Error(1) + r1 = ret.Get(1).(int) } - return r0, r1 + if rf, ok := ret.Get(2).(func(context.Context, definitions.TestReceiversConfigBodyParams) error); ok { + r2 = rf(ctx, c) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 } // AlertmanagerMock_TestReceivers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TestReceivers' @@ -864,12 +869,12 @@ func (_c *AlertmanagerMock_TestReceivers_Call) Run(run func(ctx context.Context, return _c } -func (_c *AlertmanagerMock_TestReceivers_Call) Return(_a0 *notifier.TestReceiversResult, _a1 error) *AlertmanagerMock_TestReceivers_Call { - _c.Call.Return(_a0, _a1) +func (_c *AlertmanagerMock_TestReceivers_Call) Return(_a0 *notify.TestReceiversResult, _a1 int, _a2 error) *AlertmanagerMock_TestReceivers_Call { + _c.Call.Return(_a0, _a1, _a2) return _c } -func (_c *AlertmanagerMock_TestReceivers_Call) RunAndReturn(run func(context.Context, definitions.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error)) *AlertmanagerMock_TestReceivers_Call { +func (_c *AlertmanagerMock_TestReceivers_Call) RunAndReturn(run func(context.Context, definitions.TestReceiversConfigBodyParams) (*notify.TestReceiversResult, int, error)) *AlertmanagerMock_TestReceivers_Call { _c.Call.Return(run) return _c } diff --git a/pkg/services/ngalert/notifier/legacy_storage/persist_mock.go b/pkg/services/ngalert/notifier/legacy_storage/persist_mock.go index 0520bbe03ec..03b5f825775 100644 --- a/pkg/services/ngalert/notifier/legacy_storage/persist_mock.go +++ b/pkg/services/ngalert/notifier/legacy_storage/persist_mock.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.34.2. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package legacy_storage @@ -26,6 +26,10 @@ func (_m *MockAMConfigStore) EXPECT() *MockAMConfigStore_Expecter { func (_m *MockAMConfigStore) GetLatestAlertmanagerConfiguration(ctx context.Context, orgID int64) (*models.AlertConfiguration, error) { ret := _m.Called(ctx, orgID) + if len(ret) == 0 { + panic("no return value specified for GetLatestAlertmanagerConfiguration") + } + var r0 *models.AlertConfiguration var r1 error if rf, ok := ret.Get(0).(func(context.Context, int64) (*models.AlertConfiguration, error)); ok { @@ -81,6 +85,10 @@ func (_c *MockAMConfigStore_GetLatestAlertmanagerConfiguration_Call) RunAndRetur func (_m *MockAMConfigStore) UpdateAlertmanagerConfiguration(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error { ret := _m.Called(ctx, cmd) + if len(ret) == 0 { + panic("no return value specified for UpdateAlertmanagerConfiguration") + } + var r0 error if rf, ok := ret.Get(0).(func(context.Context, *models.SaveAlertmanagerConfigurationCmd) error); ok { r0 = rf(ctx, cmd) diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager.go b/pkg/services/ngalert/notifier/multiorg_alertmanager.go index 7a1150fa843..8e0f89dceb8 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager.go @@ -68,7 +68,7 @@ type Alertmanager interface { // Receivers GetReceivers(ctx context.Context) ([]apimodels.Receiver, error) - TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*TestReceiversResult, error) + TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*alertingNotify.TestReceiversResult, int, error) TestTemplate(ctx context.Context, c apimodels.TestTemplatesConfigBodyParams) (*TestTemplatesResults, error) // Lifecycle diff --git a/pkg/services/ngalert/notifier/testreceivers.go b/pkg/services/ngalert/notifier/testreceivers.go index 44b0c7a4d2a..1ca9cf9c42d 100644 --- a/pkg/services/ngalert/notifier/testreceivers.go +++ b/pkg/services/ngalert/notifier/testreceivers.go @@ -3,34 +3,13 @@ package notifier import ( "context" "encoding/json" - "time" alertingNotify "github.com/grafana/alerting/notify" - "github.com/prometheus/alertmanager/types" - apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" ) -type TestReceiversResult struct { - Alert types.Alert - Receivers []TestReceiverResult - NotifedAt time.Time -} - -type TestReceiverResult struct { - Name string - Configs []TestReceiverConfigResult -} - -type TestReceiverConfigResult struct { - Name string - UID string - Status string - Error error -} - -func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*TestReceiversResult, error) { +func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*alertingNotify.TestReceiversResult, int, error) { receivers := make([]*alertingNotify.APIReceiver, 0, len(c.Receivers)) for _, r := range c.Receivers { integrations := make([]*alertingNotify.GrafanaIntegrationConfig, 0, len(r.GrafanaManagedReceivers)) @@ -56,37 +35,10 @@ func (am *alertmanager) TestReceivers(ctx context.Context, c apimodels.TestRecei alert = &alertingNotify.TestReceiversConfigAlertParams{Annotations: c.Alert.Annotations, Labels: c.Alert.Labels} } - result, err := am.Base.TestReceivers(ctx, alertingNotify.TestReceiversConfigBodyParams{ + return am.Base.TestReceivers(ctx, alertingNotify.TestReceiversConfigBodyParams{ Alert: alert, Receivers: receivers, }) - - if err != nil { - return nil, err - } - - resultReceivers := make([]TestReceiverResult, 0, len(result.Receivers)) - for _, resultReceiver := range result.Receivers { - configs := make([]TestReceiverConfigResult, 0, len(resultReceiver.Configs)) - for _, c := range resultReceiver.Configs { - configs = append(configs, TestReceiverConfigResult{ - Name: c.Name, - UID: c.UID, - Status: c.Status, - Error: c.Error, - }) - } - resultReceivers = append(resultReceivers, TestReceiverResult{ - Name: resultReceiver.Name, - Configs: configs, - }) - } - - return &TestReceiversResult{ - Alert: result.Alert, - Receivers: resultReceivers, - NotifedAt: result.NotifedAt, - }, err } func (am *alertmanager) GetReceivers(_ context.Context) ([]apimodels.Receiver, error) { diff --git a/pkg/services/ngalert/remote/alertmanager.go b/pkg/services/ngalert/remote/alertmanager.go index 87bfc8a1f6e..41b1066a1e9 100644 --- a/pkg/services/ngalert/remote/alertmanager.go +++ b/pkg/services/ngalert/remote/alertmanager.go @@ -509,8 +509,8 @@ func (am *Alertmanager) GetReceivers(ctx context.Context) ([]apimodels.Receiver, return am.mimirClient.GetReceivers(ctx) } -func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error) { - return ¬ifier.TestReceiversResult{}, nil +func (am *Alertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*alertingNotify.TestReceiversResult, int, error) { + return &alertingNotify.TestReceiversResult{}, 0, nil } func (am *Alertmanager) TestTemplate(ctx context.Context, c apimodels.TestTemplatesConfigBodyParams) (*notifier.TestTemplatesResults, error) { diff --git a/pkg/services/ngalert/remote/forked_alertmanager_test.go b/pkg/services/ngalert/remote/forked_alertmanager_test.go index d7ae6429871..63221c45daa 100644 --- a/pkg/services/ngalert/remote/forked_alertmanager_test.go +++ b/pkg/services/ngalert/remote/forked_alertmanager_test.go @@ -291,14 +291,14 @@ func TestForkedAlertmanager_ModeRemoteSecondary(t *testing.T) { t.Run("TestReceivers", func(tt *testing.T) { // TestReceivers should be called only in the internal Alertmanager. internal, _, forked := genTestAlertmanagers(tt, modeRemoteSecondary) - internal.EXPECT().TestReceivers(mock.Anything, mock.Anything).Return(nil, nil).Once() - _, err := forked.TestReceivers(ctx, apimodels.TestReceiversConfigBodyParams{}) + internal.EXPECT().TestReceivers(mock.Anything, mock.Anything).Return(nil, 0, nil).Once() + _, _, err := forked.TestReceivers(ctx, apimodels.TestReceiversConfigBodyParams{}) require.NoError(tt, err) // If there's an error in the internal Alertmanager, it should be returned. internal, _, forked = genTestAlertmanagers(tt, modeRemoteSecondary) - internal.EXPECT().TestReceivers(mock.Anything, mock.Anything).Return(nil, expErr).Once() - _, err = forked.TestReceivers(ctx, apimodels.TestReceiversConfigBodyParams{}) + internal.EXPECT().TestReceivers(mock.Anything, mock.Anything).Return(nil, 0, expErr).Once() + _, _, err = forked.TestReceivers(ctx, apimodels.TestReceiversConfigBodyParams{}) require.ErrorIs(tt, expErr, err) }) @@ -628,14 +628,14 @@ func TestForkedAlertmanager_ModeRemotePrimary(t *testing.T) { // TestReceivers should be called only in the remote Alertmanager. // TODO: change to remote AM once it's implemented there. internal, _, forked := genTestAlertmanagers(tt, modeRemotePrimary) - internal.EXPECT().TestReceivers(mock.Anything, mock.Anything).Return(nil, nil).Once() - _, err := forked.TestReceivers(ctx, apimodels.TestReceiversConfigBodyParams{}) + internal.EXPECT().TestReceivers(mock.Anything, mock.Anything).Return(nil, 0, nil).Once() + _, _, err := forked.TestReceivers(ctx, apimodels.TestReceiversConfigBodyParams{}) require.NoError(tt, err) // If there's an error in the remote Alertmanager, it should be returned. internal, _, forked = genTestAlertmanagers(tt, modeRemotePrimary) - internal.EXPECT().TestReceivers(mock.Anything, mock.Anything).Return(nil, expErr).Once() - _, err = forked.TestReceivers(ctx, apimodels.TestReceiversConfigBodyParams{}) + internal.EXPECT().TestReceivers(mock.Anything, mock.Anything).Return(nil, 0, expErr).Once() + _, _, err = forked.TestReceivers(ctx, apimodels.TestReceiversConfigBodyParams{}) require.ErrorIs(tt, expErr, err) }) diff --git a/pkg/services/ngalert/remote/mock/remoteAlertmanager.go b/pkg/services/ngalert/remote/mock/remoteAlertmanager.go index 8cf3983bcb0..dc73d379b3c 100644 --- a/pkg/services/ngalert/remote/mock/remoteAlertmanager.go +++ b/pkg/services/ngalert/remote/mock/remoteAlertmanager.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. +// Code generated by mockery v2.44.1. DO NOT EDIT. package alertmanager_mock @@ -13,8 +13,6 @@ import ( models "github.com/grafana/grafana/pkg/services/ngalert/models" - notifier "github.com/grafana/grafana/pkg/services/ngalert/notifier" - notify "github.com/grafana/alerting/notify" v2models "github.com/prometheus/alertmanager/api/v2/models" @@ -909,33 +907,40 @@ func (_c *RemoteAlertmanagerMock_StopAndWait_Call) RunAndReturn(run func()) *Rem } // TestReceivers provides a mock function with given fields: ctx, c -func (_m *RemoteAlertmanagerMock) TestReceivers(ctx context.Context, c definitions.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error) { +func (_m *RemoteAlertmanagerMock) TestReceivers(ctx context.Context, c definitions.TestReceiversConfigBodyParams) (*notify.TestReceiversResult, int, error) { ret := _m.Called(ctx, c) if len(ret) == 0 { panic("no return value specified for TestReceivers") } - var r0 *notifier.TestReceiversResult - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, definitions.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error)); ok { + var r0 *notify.TestReceiversResult + var r1 int + var r2 error + if rf, ok := ret.Get(0).(func(context.Context, definitions.TestReceiversConfigBodyParams) (*notify.TestReceiversResult, int, error)); ok { return rf(ctx, c) } - if rf, ok := ret.Get(0).(func(context.Context, definitions.TestReceiversConfigBodyParams) *notifier.TestReceiversResult); ok { + if rf, ok := ret.Get(0).(func(context.Context, definitions.TestReceiversConfigBodyParams) *notify.TestReceiversResult); ok { r0 = rf(ctx, c) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(*notifier.TestReceiversResult) + r0 = ret.Get(0).(*notify.TestReceiversResult) } } - if rf, ok := ret.Get(1).(func(context.Context, definitions.TestReceiversConfigBodyParams) error); ok { + if rf, ok := ret.Get(1).(func(context.Context, definitions.TestReceiversConfigBodyParams) int); ok { r1 = rf(ctx, c) } else { - r1 = ret.Error(1) + r1 = ret.Get(1).(int) } - return r0, r1 + if rf, ok := ret.Get(2).(func(context.Context, definitions.TestReceiversConfigBodyParams) error); ok { + r2 = rf(ctx, c) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 } // RemoteAlertmanagerMock_TestReceivers_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'TestReceivers' @@ -957,12 +962,12 @@ func (_c *RemoteAlertmanagerMock_TestReceivers_Call) Run(run func(ctx context.Co return _c } -func (_c *RemoteAlertmanagerMock_TestReceivers_Call) Return(_a0 *notifier.TestReceiversResult, _a1 error) *RemoteAlertmanagerMock_TestReceivers_Call { - _c.Call.Return(_a0, _a1) +func (_c *RemoteAlertmanagerMock_TestReceivers_Call) Return(_a0 *notify.TestReceiversResult, _a1 int, _a2 error) *RemoteAlertmanagerMock_TestReceivers_Call { + _c.Call.Return(_a0, _a1, _a2) return _c } -func (_c *RemoteAlertmanagerMock_TestReceivers_Call) RunAndReturn(run func(context.Context, definitions.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error)) *RemoteAlertmanagerMock_TestReceivers_Call { +func (_c *RemoteAlertmanagerMock_TestReceivers_Call) RunAndReturn(run func(context.Context, definitions.TestReceiversConfigBodyParams) (*notify.TestReceiversResult, int, error)) *RemoteAlertmanagerMock_TestReceivers_Call { _c.Call.Return(run) return _c } diff --git a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go index 1f8f349fc98..be783deef4e 100644 --- a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go @@ -118,7 +118,7 @@ func (fam *RemotePrimaryForkedAlertmanager) GetReceivers(ctx context.Context) ([ return fam.remote.GetReceivers(ctx) } -func (fam *RemotePrimaryForkedAlertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error) { +func (fam *RemotePrimaryForkedAlertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*alertingNotify.TestReceiversResult, int, error) { // TODO: change to remote AM once it's implemented there. return fam.internal.TestReceivers(ctx, c) } diff --git a/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go index 5f9c9d3f641..8143c29ba79 100644 --- a/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_secondary_forked_alertmanager.go @@ -159,7 +159,7 @@ func (fam *RemoteSecondaryForkedAlertmanager) GetReceivers(ctx context.Context) return fam.internal.GetReceivers(ctx) } -func (fam *RemoteSecondaryForkedAlertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*notifier.TestReceiversResult, error) { +func (fam *RemoteSecondaryForkedAlertmanager) TestReceivers(ctx context.Context, c apimodels.TestReceiversConfigBodyParams) (*alertingNotify.TestReceiversResult, int, error) { return fam.internal.TestReceivers(ctx, c) } From d342e76f636e3a5751c5e7baccd1c8910b50d863 Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Mon, 12 Aug 2024 16:39:31 +0200 Subject: [PATCH 029/229] Chore: Add skeleton for background plugin installer (#91743) --- .../feature-toggles/index.md | 1 + .../src/types/featureToggles.gen.ts | 1 + .../backgroundsvcs/background_services.go | 3 ++ pkg/services/featuremgmt/registry.go | 7 ++++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 +++ pkg/services/featuremgmt/toggles_gen.json | 13 ++++++++ .../plugininstaller/service.go | 32 +++++++++++++++++++ .../plugininstaller/service_test.go | 20 ++++++++++++ .../pluginsintegration/pluginsintegration.go | 2 ++ 10 files changed, 84 insertions(+) create mode 100644 pkg/services/pluginsintegration/plugininstaller/service.go create mode 100644 pkg/services/pluginsintegration/plugininstaller/service_test.go diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 70a4dcfdae9..97781c967bb 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -191,6 +191,7 @@ Experimental features might be changed or removed without prior notice. | `databaseReadReplica` | Use a read replica for some database queries. | | `alertingApiServer` | Register Alerting APIs with the K8s API server | | `dashboardRestoreUI` | Enables the frontend to be able to restore a recently deleted dashboard | +| `backgroundPluginInstaller` | Enable background plugin installer | | `dataplaneAggregator` | Enable grafana dataplane aggregator | | `adhocFilterOneOf` | Exposes a new 'one of' operator for ad-hoc filters. This operator allows users to filter by multiple values in a single filter. | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index ec4c39521c1..20a0390eeb4 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -200,6 +200,7 @@ export interface FeatureToggles { bodyScrolling?: boolean; cloudwatchMetricInsightsCrossAccount?: boolean; prometheusAzureOverrideAudience?: boolean; + backgroundPluginInstaller?: boolean; dataplaneAggregator?: boolean; adhocFilterOneOf?: boolean; } diff --git a/pkg/registry/backgroundsvcs/background_services.go b/pkg/registry/backgroundsvcs/background_services.go index 4333bfebd04..fe193b09d7f 100644 --- a/pkg/registry/backgroundsvcs/background_services.go +++ b/pkg/registry/backgroundsvcs/background_services.go @@ -29,6 +29,7 @@ import ( "github.com/grafana/grafana/pkg/services/pluginsintegration/angulardetectorsprovider" "github.com/grafana/grafana/pkg/services/pluginsintegration/keyretriever/dynamic" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginexternal" + "github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller" pluginStore "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/services/provisioning" publicdashboardsmetric "github.com/grafana/grafana/pkg/services/publicdashboards/metric" @@ -63,6 +64,7 @@ func ProvideBackgroundServiceRegistry( anon *anonimpl.AnonDeviceService, ssoSettings *ssosettingsimpl.Service, pluginExternal *pluginexternal.Service, + pluginInstaller *plugininstaller.Service, // Need to make sure these are initialized, is there a better place to put them? _ dashboardsnapshots.Service, _ serviceaccounts.Service, _ *guardian.Provider, @@ -105,6 +107,7 @@ func ProvideBackgroundServiceRegistry( anon, ssoSettings, pluginExternal, + pluginInstaller, ) } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 9690b54ba1f..942bbe4a61a 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1379,6 +1379,13 @@ var ( Owner: grafanaPartnerPluginsSquad, Expression: "true", // Enabled by default for now }, + { + Name: "backgroundPluginInstaller", + Description: "Enable background plugin installer", + Stage: FeatureStageExperimental, + Owner: grafanaPluginsPlatformSquad, + RequiresRestart: true, + }, { Name: "dataplaneAggregator", Description: "Enable grafana dataplane aggregator", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index f866c897069..415fdd789ca 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -181,5 +181,6 @@ cloudWatchRoundUpEndTime,GA,@grafana/aws-datasources,false,false,false bodyScrolling,preview,@grafana/grafana-frontend-platform,false,false,true cloudwatchMetricInsightsCrossAccount,preview,@grafana/aws-datasources,false,false,true prometheusAzureOverrideAudience,deprecated,@grafana/partner-datasources,false,false,false +backgroundPluginInstaller,experimental,@grafana/plugins-platform-backend,false,true,false dataplaneAggregator,experimental,@grafana/grafana-app-platform-squad,false,true,false adhocFilterOneOf,experimental,@grafana/dashboards-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 1aa69d2517b..ac06c13d7e2 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -735,6 +735,10 @@ const ( // Deprecated. Allow override default AAD audience for Azure Prometheus endpoint. Enabled by default. This feature should no longer be used and will be removed in the future. FlagPrometheusAzureOverrideAudience = "prometheusAzureOverrideAudience" + // FlagBackgroundPluginInstaller + // Enable background plugin installer + FlagBackgroundPluginInstaller = "backgroundPluginInstaller" + // FlagDataplaneAggregator // Enable grafana dataplane aggregator FlagDataplaneAggregator = "dataplaneAggregator" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index e4be36069f0..ede7702b5a5 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -529,6 +529,19 @@ "codeowner": "@grafana/partner-datasources" } }, + { + "metadata": { + "name": "backgroundPluginInstaller", + "resourceVersion": "1723202510081", + "creationTimestamp": "2024-08-09T11:21:50Z" + }, + "spec": { + "description": "Enable background plugin installer", + "stage": "experimental", + "codeowner": "@grafana/plugins-platform-backend", + "requiresRestart": true + } + }, { "metadata": { "name": "bodyScrolling", diff --git a/pkg/services/pluginsintegration/plugininstaller/service.go b/pkg/services/pluginsintegration/plugininstaller/service.go new file mode 100644 index 00000000000..31d9127eb36 --- /dev/null +++ b/pkg/services/pluginsintegration/plugininstaller/service.go @@ -0,0 +1,32 @@ +package plugininstaller + +import ( + "context" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" +) + +type Service struct { + features featuremgmt.FeatureToggles + log log.Logger +} + +func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles) *Service { + s := &Service{ + features: features, + log: log.New("plugin.installer"), + } + return s +} + +// IsDisabled disables background installation of plugins. +func (s *Service) IsDisabled() bool { + return !s.features.IsEnabled(context.Background(), featuremgmt.FlagBackgroundPluginInstaller) +} + +func (s *Service) Run(ctx context.Context) error { + s.log.Debug("PluginInstaller.Run not implemented") + return nil +} diff --git a/pkg/services/pluginsintegration/plugininstaller/service_test.go b/pkg/services/pluginsintegration/plugininstaller/service_test.go new file mode 100644 index 00000000000..09ad3dfdfa6 --- /dev/null +++ b/pkg/services/pluginsintegration/plugininstaller/service_test.go @@ -0,0 +1,20 @@ +package plugininstaller + +import ( + "testing" + + "github.com/grafana/grafana/pkg/services/featuremgmt" +) + +// Test if the service is disabled +func TestService_IsDisabled(t *testing.T) { + // Create a new service + s := &Service{ + features: featuremgmt.WithFeatures(featuremgmt.FlagBackgroundPluginInstaller), + } + + // Check if the service is disabled + if s.IsDisabled() { + t.Error("Service should be enabled") + } +} diff --git a/pkg/services/pluginsintegration/pluginsintegration.go b/pkg/services/pluginsintegration/pluginsintegration.go index 64262eecede..bf0e7705fc0 100644 --- a/pkg/services/pluginsintegration/pluginsintegration.go +++ b/pkg/services/pluginsintegration/pluginsintegration.go @@ -47,6 +47,7 @@ import ( "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginexternal" + "github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" @@ -124,6 +125,7 @@ var WireSet = wire.NewSet( pluginexternal.ProvideService, plugincontext.ProvideBaseService, wire.Bind(new(plugincontext.BasePluginContextProvider), new(*plugincontext.BaseProvider)), + plugininstaller.ProvideService, ) // WireExtensionSet provides a wire.ProviderSet of plugin providers that can be From 5e638b4af0d25dd40da53ca5ebd0f6ebf3f58d23 Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Mon, 12 Aug 2024 10:53:02 -0400 Subject: [PATCH 030/229] Docs: add playlist management permissions (#91146) --- docs/sources/dashboards/create-manage-playlists/index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/sources/dashboards/create-manage-playlists/index.md b/docs/sources/dashboards/create-manage-playlists/index.md index 0b916eeb488..07e51ee44b7 100644 --- a/docs/sources/dashboards/create-manage-playlists/index.md +++ b/docs/sources/dashboards/create-manage-playlists/index.md @@ -26,6 +26,10 @@ Grafana automatically scales dashboards to any resolution, which makes them perf You can access the Playlist feature from Grafana's side menu, in the Dashboards submenu. +{{< admonition type="note" >}} +You must have at least Editor role permissions to create and manage playlists. +{{< /admonition >}} + ## Access, share, and control a playlist Use the information in this section to access playlists. Start and control the display of a playlist using one of the six available modes. From 8fb334cf77cd633d0330dcf22fc1a31fc7a190dd Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Mon, 12 Aug 2024 13:48:14 -0600 Subject: [PATCH 031/229] Unified Storage: Adds back readme to resource store (#91812) adds back readme to resource store --- pkg/storage/unified/README.md | 205 ++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 pkg/storage/unified/README.md diff --git a/pkg/storage/unified/README.md b/pkg/storage/unified/README.md new file mode 100644 index 00000000000..c43637f1d90 --- /dev/null +++ b/pkg/storage/unified/README.md @@ -0,0 +1,205 @@ + +# Unified Storage + +The unified storage projects aims to provide a simple and extensible backend to unify the way we store different objects within the Grafana app platform. + +It provides generic storage for k8s objects, and can store data either within dedicated tables in the main Grafana database, or in separate storage. + +By default it runs in-process within Grafana, but it can also be run as a standalone GRPC service (`storage-server`). + +## Storage Overview + +There are 2 main tables, the `resource` table stores a "current" view of the objects, and the `resource_history` table stores a record of each revision of a given object. + +## Running Unified Storage + +### Baseline configuration + +The minimum config settings required are: + +```ini +; need to specify target here for override to work later +target = all + +[server] +; https is required for kubectl +protocol = https + +[feature_toggles] +; enable unified storage +unifiedStorage = true +; enable k8s apiserver +grafanaAPIServer = true +; store playlists in k8s +kubernetesPlaylists = true +; store json id token in context +idForwarding = true + +[grafana-apiserver] +; use unified storage for k8s apiserver +storage_type = unified +``` + +With this configuration, you can run everything in-process. Run the Grafana backend with: + +```sh +bra run +``` + +or + +```sh +make run +``` + +The default kubeconfig sends requests directly to the apiserver, to authenticate as a grafana user, create `grafana.kubeconfig`: +```yaml +apiVersion: v1 +clusters: +- cluster: + insecure-skip-tls-verify: true + server: https://127.0.0.1:3000 + name: default-cluster +contexts: +- context: + cluster: default-cluster + namespace: default + user: default + name: default-context +current-context: default-context +kind: Config +preferences: {} +users: +- name: default + user: + username: + password: +``` +Where `` and `` are credentials for basic auth against Grafana. For example, with the [default credentials](https://github.com/grafana/grafana/blob/HEAD/contribute/developer-guide.md#backend): +```yaml + username: admin + password: admin +``` + +In this mode, you can interact with the k8s api. Make sure you are in the directory where you created `grafana.kubeconfig`. Then run: +```sh +kubectl --kubeconfig=./grafana.kubeconfig get playlist +``` + +If this is your first time running the command, a successful response would be: +```sh +No resources found in default namespace. +``` + +To create a playlist, create a file `playlist-generate.yaml`: +```yaml +apiVersion: playlist.grafana.app/v0alpha1 +kind: Playlist +metadata: + generateName: x # anything is ok here... except yes or true -- they become boolean! + labels: + foo: bar + annotations: + grafana.app/slug: "slugger" + grafana.app/updatedBy: "updater" +spec: + title: Playlist with auto generated UID + interval: 5m + items: + - type: dashboard_by_tag + value: panel-tests + - type: dashboard_by_uid + value: vmie2cmWz # dashboard from devenv +``` +then run: +```sh +kubectl --kubeconfig=./grafana.kubeconfig create -f playlist-generate.yaml +``` + +For example, a successful response would be: +```sh +playlist.playlist.grafana.app/u394j4d3-s63j-2d74-g8hf-958773jtybf2 created +``` + +When running +```sh +kubectl --kubeconfig=./grafana.kubeconfig get playlist +``` +you should now see something like: +```sh +NAME TITLE INTERVAL CREATED AT +u394j4d3-s63j-2d74-g8hf-958773jtybf2 Playlist with auto generated UID 5m 2023-12-14T13:53:35Z +``` + +To update the playlist, update the `playlist-generate.yaml` file then run: +```sh +kubectl --kubeconfig=./grafana.kubeconfig patch playlist --patch-file playlist-generate.yaml +``` + +In the example, `` would be `u394j4d3-s63j-2d74-g8hf-958773jtybf2`. + +### Use a separate database + +By default Unified Storage uses the Grafana database. To run against a separate database, update `custom.ini` by adding the following section to it: + +``` +[resource_api] +db_type = mysql +db_host = localhost:3306 +db_name = grafana +db_user = +db_pass = +``` + +MySQL and Postgres are both supported. The `` and `` values can be found in the following devenv docker compose files: [MySQL](https://github.com/grafana/grafana/blob/main/devenv/docker/blocks/mysql/docker-compose.yaml#L6-L7) and [Postgres](https://github.com/grafana/grafana/blob/main/devenv/docker/blocks/postgres/docker-compose.yaml#L4-L5). + +Then, run +```sh +make devenv sources= +``` +where source is either `mysql` or `postgres`. + +Finally, run the Grafana backend with + +```sh +bra run +``` +or +```sh +make run +``` + +### Run as a GRPC service + +#### Start GRPC storage-server + +This currently only works with a separate database configuration (see previous section). + +Start the storage-server with: +```sh +GF_DEFAULT_TARGET=storage-server ./bin/grafana server target +``` + +The GRPC service will listen on port 10000 + +#### Use GRPC server + +To run grafana against the storage-server, override the `storage_type` setting: +```sh +GF_GRAFANA_APISERVER_STORAGE_TYPE=unified-grpc ./bin/grafana server +``` + +You can then list the previously-created playlists with: +```sh +kubectl --kubeconfig=./grafana.kubeconfig get playlist +``` + +## Changing protobuf interface + +- install [protoc](https://grpc.io/docs/protoc-installation/) +- install the protocol compiler plugin for Go +```sh +go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest +``` +- make changes in `.proto` file +- to compile all protobuf files in the repository run `make protobuf` at its top level From d54fdba322dcf1cedb38e151c72d1b1a8f26654f Mon Sep 17 00:00:00 2001 From: Sam Jewell <2903904+samjewell@users.noreply.github.com> Date: Mon, 12 Aug 2024 21:00:10 +0100 Subject: [PATCH 032/229] Bump to go-duck v0.1.0 (#91794) * Bump to go-duck v0.1.0 This fixed ordering of the columns **What is this feature, why do we need it?** See https://github.com/scottlepp/go-duck/pull/14 for a description of the improvement we're including here **Who is this feature for?** Anyone who uses SQL Expressions. This is still an experimental feature, and only used by a very small number of instances. * Run `make update-workspace` --- go.mod | 8 ++++++-- go.sum | 15 +++++++-------- go.work.sum | 4 ++++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 3869249170e..48bb6aee0ec 100644 --- a/go.mod +++ b/go.mod @@ -144,7 +144,7 @@ require ( github.com/redis/go-redis/v9 v9.1.0 // @grafana/alerting-backend github.com/robfig/cron/v3 v3.0.1 // @grafana/grafana-backend-group github.com/russellhaering/goxmldsig v1.4.0 // @grafana/grafana-backend-group - github.com/scottlepp/go-duck v0.0.21 // @grafana/grafana-app-platform-squad + github.com/scottlepp/go-duck v0.1.0 // @grafana/grafana-app-platform-squad github.com/spf13/cobra v1.8.1 // @grafana/grafana-app-platform-squad github.com/spf13/pflag v1.0.5 // @grafana-app-platform-squad github.com/spyzhov/ajson v0.9.0 // @grafana/grafana-app-platform-squad @@ -231,7 +231,6 @@ require ( github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect github.com/antlr4-go/antlr/v4 v4.13.0 // indirect - github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 // indirect github.com/apache/thrift v0.20.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect @@ -481,6 +480,11 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect ) +require ( + github.com/hairyhenderson/go-which v0.2.0 // indirect + github.com/iancoleman/orderedmap v0.3.0 // indirect +) + // Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream replace github.com/crewjam/saml => github.com/grafana/saml v0.4.15-0.20240523142256-cc370b98af7c diff --git a/go.sum b/go.sum index 73da8d916a2..3c695c8b9e1 100644 --- a/go.sum +++ b/go.sum @@ -1536,8 +1536,6 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/apache/arrow/go/arrow v0.0.0-20210223225224-5bea62493d91/go.mod h1:c9sxoIT3YgLxH4UhLOCKaBlEojuMhVYpk4Ntv3opUTQ= -github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 h1:q4dksr6ICHXqG5hm0ZW5IHyeEJXoIJSOZeBLmWPNeIQ= -github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/arrow/go/v12 v12.0.0/go.mod h1:d+tV/eHZZ7Dz7RPrFKtPK02tpr+c9/PEd/zm8mDS9Vg= @@ -2176,7 +2174,6 @@ github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl76 github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= @@ -2395,6 +2392,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYp github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hairyhenderson/go-which v0.2.0 h1:vxoCKdgYc6+MTBzkJYhWegksHjjxuXPNiqo5G2oBM+4= +github.com/hairyhenderson/go-which v0.2.0/go.mod h1:U1BQQRCjxYHfOkXDyCgst7OZVknbqI7KuGKhGnmyIik= github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= @@ -2501,6 +2500,8 @@ github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4 github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= +github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc= +github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE= github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -2651,7 +2652,6 @@ github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCy github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= @@ -3023,7 +3023,6 @@ github.com/phpdave11/gofpdi v1.0.13 h1:o61duiW8M9sMlkVXWlvP92sZJtGKENvW3VExs6dZu github.com/phpdave11/gofpdi v1.0.13/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= -github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= @@ -3176,8 +3175,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0 github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26 h1:F+GIVtGqCFxPxO46ujf8cEOP574MBoRm3gNbPXECbxs= github.com/scaleway/scaleway-sdk-go v1.0.0-beta.26/go.mod h1:fCa7OJZ/9DRTnOKmxvT6pn+LPWUptQAmHF/SBJUGEcg= -github.com/scottlepp/go-duck v0.0.21 h1:bFg5/8ULOo62vmvIjEOy1EOf7Q86cpzq82BDN5RakVE= -github.com/scottlepp/go-duck v0.0.21/go.mod h1:m6V1VGZ4hdgvCj6+BmNMFo0taqiWhMx3CeL3uKHmP2E= +github.com/scottlepp/go-duck v0.1.0 h1:Lfunl1wd767v0dF0/dr+mBh+KnUFuDmgNycC76NJjeE= +github.com/scottlepp/go-duck v0.1.0/go.mod h1:xGoYUbgph5AbxwsMElWv2i/mgzQl89WIgwE69Ytml7Q= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg= @@ -4333,7 +4332,6 @@ google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxH google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= @@ -4682,6 +4680,7 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/go.work.sum b/go.work.sum index dc231d9c2a7..68666cbfc08 100644 --- a/go.work.sum +++ b/go.work.sum @@ -209,6 +209,8 @@ github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEq github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= +github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 h1:q4dksr6ICHXqG5hm0ZW5IHyeEJXoIJSOZeBLmWPNeIQ= +github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= github.com/apache/arrow/go/v10 v10.0.1 h1:n9dERvixoC/1JjDmBcs9FPaEryoANa2sCgVFo6ez9cI= github.com/apache/arrow/go/v11 v11.0.0 h1:hqauxvFQxww+0mEU/2XHG6LT7eZternCZq+A5Yly2uM= github.com/apache/arrow/go/v12 v12.0.1 h1:JsR2+hzYYjgSUkBSaahpqCetqZMr76djX80fF/DiJbg= @@ -383,6 +385,8 @@ github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= +github.com/google/cel-go v0.17.7 h1:6ebJFzu1xO2n7TLtN+UBqShGBhlD85bhvglh5DpcfqQ= +github.com/google/cel-go v0.17.7/go.mod h1:HXZKzB0LXqer5lHHgfWAnlYwJaQBDKMjxjulNQzhwhY= github.com/google/go-jsonnet v0.18.0 h1:/6pTy6g+Jh1a1I2UMoAODkqELFiVIdOxbNwv0DDzoOg= github.com/google/go-jsonnet v0.18.0/go.mod h1:C3fTzyVJDslXdiTqw/bTFk7vSGyCtH3MGRbDfvEwGd0= github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9 h1:OF1IPgv+F4NmqmJ98KTjdN97Vs1JxDPB3vbmYzV2dpk= From b67bcdb9b85fee973141a9ed2e20ad3eb4d6c944 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Tue, 13 Aug 2024 08:31:07 +0200 Subject: [PATCH 033/229] Alerting: Handle namespace and group query string params in Ruler API (#91533) * Handle namespace and group query string params in Ruler API * Use the new namespace and group query params when slashes in names * Add validation, add group handling in GMA Api * Move constants * Use checkForPathSeparator function * Fix linter issue --- pkg/services/ngalert/api/api_ruler.go | 23 +++- pkg/services/ngalert/api/lotex_ruler.go | 54 +++++++-- pkg/services/ngalert/api/util.go | 31 +++++ .../alerting/unified/api/alertRuleApi.ts | 5 +- .../alerting/unified/api/prometheus.ts | 16 +-- .../alerting/unified/api/ruler.test.ts | 108 +++++++++++------- .../features/alerting/unified/api/ruler.ts | 95 ++++++++++++--- 7 files changed, 251 insertions(+), 81 deletions(-) diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 15c6d42a9b6..27aca213e35 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -85,8 +85,14 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceU "namespaceUid", namespace.UID, } - if group != "" { - loggerCtx = append(loggerCtx, "group", group) + + finalGroup, err := getRulesGroupParam(c, group) + if err != nil { + return ErrResp(http.StatusBadRequest, err, "") + } + + if finalGroup != "" { + loggerCtx = append(loggerCtx, "group", finalGroup) } logger := srv.log.New(loggerCtx...) @@ -97,11 +103,11 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceU err = srv.xactManager.InTransaction(c.Req.Context(), func(ctx context.Context) error { deletionCandidates := map[ngmodels.AlertRuleGroupKey]ngmodels.RulesGroup{} - if group != "" { + if finalGroup != "" { key := ngmodels.AlertRuleGroupKey{ OrgID: c.SignedInUser.GetOrgID(), NamespaceUID: namespace.UID, - RuleGroup: group, + RuleGroup: finalGroup, } rules, err := srv.getAuthorizedRuleGroup(ctx, c, key) if err != nil { @@ -218,9 +224,14 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *contextmodel.ReqContext, namespa return toNamespaceErrorResponse(err) } + finalRuleGroup, err := getRulesGroupParam(c, ruleGroup) + if err != nil { + return ErrResp(http.StatusBadRequest, err, "") + } + rules, err := srv.getAuthorizedRuleGroup(c.Req.Context(), c, ngmodels.AlertRuleGroupKey{ OrgID: c.SignedInUser.GetOrgID(), - RuleGroup: ruleGroup, + RuleGroup: finalRuleGroup, NamespaceUID: namespace.UID, }) if err != nil { @@ -234,7 +245,7 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *contextmodel.ReqContext, namespa result := apimodels.RuleGroupConfigResponse{ // nolint:staticcheck - GettableRuleGroupConfig: toGettableRuleGroupConfig(ruleGroup, rules, provenanceRecords), + GettableRuleGroupConfig: toGettableRuleGroupConfig(finalRuleGroup, rules, provenanceRecords), } return response.JSON(http.StatusAccepted, result) } diff --git a/pkg/services/ngalert/api/lotex_ruler.go b/pkg/services/ngalert/api/lotex_ruler.go index bcffe81c63f..7e920dbc136 100644 --- a/pkg/services/ngalert/api/lotex_ruler.go +++ b/pkg/services/ngalert/api/lotex_ruler.go @@ -68,12 +68,18 @@ func (r *LotexRuler) RouteDeleteNamespaceRulesConfig(ctx *contextmodel.ReqContex if err != nil { return ErrResp(500, err, "") } + + finalNamespace, err := getRulesNamespaceParam(ctx, namespace) + if err != nil { + return ErrResp(http.StatusBadRequest, err, "") + } + return r.requester.withReq( ctx, http.MethodDelete, withPath( *ctx.Req.URL, - fmt.Sprintf("%s/%s", legacyRulerPrefix, url.PathEscape(namespace)), + fmt.Sprintf("%s/%s", legacyRulerPrefix, url.PathEscape(finalNamespace)), ), nil, messageExtractor, @@ -86,6 +92,17 @@ func (r *LotexRuler) RouteDeleteRuleGroupConfig(ctx *contextmodel.ReqContext, na if err != nil { return ErrResp(500, err, "") } + + finalNamespace, err := getRulesNamespaceParam(ctx, namespace) + if err != nil { + return ErrResp(http.StatusBadRequest, err, "") + } + + finalGroup, err := getRulesGroupParam(ctx, group) + if err != nil { + return ErrResp(http.StatusBadRequest, err, "") + } + return r.requester.withReq( ctx, http.MethodDelete, @@ -94,8 +111,8 @@ func (r *LotexRuler) RouteDeleteRuleGroupConfig(ctx *contextmodel.ReqContext, na fmt.Sprintf( "%s/%s/%s", legacyRulerPrefix, - url.PathEscape(namespace), - url.PathEscape(group), + url.PathEscape(finalNamespace), + url.PathEscape(finalGroup), ), ), nil, @@ -109,6 +126,12 @@ func (r *LotexRuler) RouteGetNamespaceRulesConfig(ctx *contextmodel.ReqContext, if err != nil { return ErrResp(500, err, "") } + + finalNamespace, err := getRulesNamespaceParam(ctx, namespace) + if err != nil { + return ErrResp(http.StatusBadRequest, err, "") + } + return r.requester.withReq( ctx, http.MethodGet, @@ -117,7 +140,7 @@ func (r *LotexRuler) RouteGetNamespaceRulesConfig(ctx *contextmodel.ReqContext, fmt.Sprintf( "%s/%s", legacyRulerPrefix, - url.PathEscape(namespace), + url.PathEscape(finalNamespace), ), ), nil, @@ -131,6 +154,17 @@ func (r *LotexRuler) RouteGetRulegGroupConfig(ctx *contextmodel.ReqContext, name if err != nil { return ErrResp(500, err, "") } + + finalNamespace, err := getRulesNamespaceParam(ctx, namespace) + if err != nil { + return ErrResp(http.StatusBadRequest, err, "") + } + + finalGroup, err := getRulesGroupParam(ctx, group) + if err != nil { + return ErrResp(http.StatusBadRequest, err, "") + } + return r.requester.withReq( ctx, http.MethodGet, @@ -139,8 +173,8 @@ func (r *LotexRuler) RouteGetRulegGroupConfig(ctx *contextmodel.ReqContext, name fmt.Sprintf( "%s/%s/%s", legacyRulerPrefix, - url.PathEscape(namespace), - url.PathEscape(group), + url.PathEscape(finalNamespace), + url.PathEscape(finalGroup), ), ), nil, @@ -177,7 +211,13 @@ func (r *LotexRuler) RoutePostNameRulesConfig(ctx *contextmodel.ReqContext, conf if err != nil { return ErrResp(500, err, "Failed marshal rule group") } - u := withPath(*ctx.Req.URL, fmt.Sprintf("%s/%s", legacyRulerPrefix, ns)) + + finalNamespace, err := getRulesNamespaceParam(ctx, ns) + if err != nil { + return ErrResp(http.StatusBadRequest, err, "") + } + + u := withPath(*ctx.Req.URL, fmt.Sprintf("%s/%s", legacyRulerPrefix, url.PathEscape(finalNamespace))) return r.requester.withReq(ctx, http.MethodPost, u, bytes.NewBuffer(yml), jsonExtractor(nil), nil) } diff --git a/pkg/services/ngalert/api/util.go b/pkg/services/ngalert/api/util.go index 5b81f27b30e..6c320715d19 100644 --- a/pkg/services/ngalert/api/util.go +++ b/pkg/services/ngalert/api/util.go @@ -26,6 +26,11 @@ import ( "github.com/grafana/grafana/pkg/web" ) +const ( + namespaceQueryTag = "QUERY_NAMESPACE" + groupQueryTag = "QUERY_GROUP" +) + var searchRegex = regexp.MustCompile(`\{(\w+)\}`) func toMacaronPath(path string) string { @@ -240,3 +245,29 @@ func getHash(hashSlice []string) uint64 { hash := sum.Sum64() return hash } + +func getRulesGroupParam(ctx *contextmodel.ReqContext, pathGroup string) (string, error) { + if pathGroup == groupQueryTag { + group := ctx.Query("group") + if group == "" { + return "", fmt.Errorf("group query parameter is empty") + } + + return group, nil + } + + return pathGroup, nil +} + +func getRulesNamespaceParam(ctx *contextmodel.ReqContext, pathNamespace string) (string, error) { + if pathNamespace == namespaceQueryTag { + namespace := ctx.Query("namespace") + if namespace == "" { + return "", fmt.Errorf("namespace query parameter is empty") + } + + return namespace, nil + } + + return pathNamespace, nil +} diff --git a/public/app/features/alerting/unified/api/alertRuleApi.ts b/public/app/features/alerting/unified/api/alertRuleApi.ts index fd8d5726ac0..3441dde01f9 100644 --- a/public/app/features/alerting/unified/api/alertRuleApi.ts +++ b/public/app/features/alerting/unified/api/alertRuleApi.ts @@ -27,7 +27,7 @@ import { FetchPromRulesFilter, groupRulesByFileName, paramsWithMatcherAndState, - prepareRulesFilterQueryParams, + getRulesFilterSearchParams, } from './prometheus'; import { FetchRulerRulesFilter, rulerUrlBuilder } from './ruler'; @@ -153,7 +153,8 @@ export const alertRuleApi = alertingApi.injectEndpoints({ searchParams.set(PrometheusAPIFilters.RuleGroup, identifier.groupName); } - const params = prepareRulesFilterQueryParams(searchParams, filter); + const filterParams = getRulesFilterSearchParams(filter); + const params = { ...filterParams, ...Object.fromEntries(searchParams) }; return { url: PROM_RULES_URL, params: paramsWithMatcherAndState(params, state, matcher) }; }, diff --git a/public/app/features/alerting/unified/api/prometheus.ts b/public/app/features/alerting/unified/api/prometheus.ts index 9a5c20bcd04..a18e1aa9294 100644 --- a/public/app/features/alerting/unified/api/prometheus.ts +++ b/public/app/features/alerting/unified/api/prometheus.ts @@ -37,8 +37,9 @@ export function prometheusUrlBuilder(dataSourceConfig: PrometheusDataSourceConfi searchParams.set('rule_group', identifier.groupName); } - const params = prepareRulesFilterQueryParams(searchParams, filter); + const filterParams = getRulesFilterSearchParams(filter); + const params = { ...filterParams, ...Object.fromEntries(searchParams) }; return { url: `/api/prometheus/${getDatasourceAPIUid(dataSourceName)}/api/v1/rules`, params: paramsWithMatcherAndState(params, state, matcher), @@ -47,18 +48,17 @@ export function prometheusUrlBuilder(dataSourceConfig: PrometheusDataSourceConfi }; } -export function prepareRulesFilterQueryParams( - params: URLSearchParams, - filter?: FetchPromRulesFilter -): Record { +export function getRulesFilterSearchParams(filter?: FetchPromRulesFilter): Record { + const filterParams: Record = {}; + if (filter?.dashboardUID) { - params.set('dashboard_uid', filter.dashboardUID); + filterParams.dashboard_uid = filter.dashboardUID; if (filter?.panelId) { - params.set('panel_id', String(filter.panelId)); + filterParams.panel_id = String(filter.panelId); } } - return Object.fromEntries(params); + return filterParams; } export function paramsWithMatcherAndState( diff --git a/public/app/features/alerting/unified/api/ruler.test.ts b/public/app/features/alerting/unified/api/ruler.test.ts index b31cdd3efd2..a4f69e49ee4 100644 --- a/public/app/features/alerting/unified/api/ruler.test.ts +++ b/public/app/features/alerting/unified/api/ruler.test.ts @@ -1,15 +1,28 @@ import { RulerDataSourceConfig } from 'app/types/unified-alerting'; -import { getDatasourceAPIUid } from '../utils/datasource'; +import { mockDataSource } from '../mocks'; +import { setupDataSources } from '../testSetup/datasources'; +import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; import { rulerUrlBuilder } from './ruler'; -jest.mock('../utils/datasource'); +const grafanaConfig: RulerDataSourceConfig = { + dataSourceName: GRAFANA_RULES_SOURCE_NAME, + apiVersion: 'legacy', +}; -const mocks = { - getDatasourceAPIUId: jest.mocked(getDatasourceAPIUid), +const mimirConfig: RulerDataSourceConfig = { + dataSourceName: 'Mimir-cloud', + apiVersion: 'config', }; +beforeAll(() => { + setupDataSources( + mockDataSource({ type: DataSourceType.Prometheus, name: 'Mimir-cloud', uid: 'mimir-1' }), + mockDataSource({ type: DataSourceType.Prometheus, name: 'Cortex', uid: 'cortex-1' }) + ); +}); + describe('rulerUrlBuilder', () => { it('Should use /api/v1/rules endpoint with subtype = cortex param for legacy api version', () => { // Arrange @@ -18,8 +31,6 @@ describe('rulerUrlBuilder', () => { apiVersion: 'legacy', }; - mocks.getDatasourceAPIUId.mockReturnValue('ds-uid'); - // Act const builder = rulerUrlBuilder(config); @@ -28,54 +39,38 @@ describe('rulerUrlBuilder', () => { const group = builder.namespaceGroup('test-ns', 'test-gr'); // Assert - expect(rules.path).toBe('/api/ruler/ds-uid/api/v1/rules'); + expect(rules.path).toBe('/api/ruler/cortex-1/api/v1/rules'); expect(rules.params).toMatchObject({ subtype: 'cortex' }); - expect(namespace.path).toBe('/api/ruler/ds-uid/api/v1/rules/test-ns'); + expect(namespace.path).toBe('/api/ruler/cortex-1/api/v1/rules/test-ns'); expect(namespace.params).toMatchObject({ subtype: 'cortex' }); - expect(group.path).toBe('/api/ruler/ds-uid/api/v1/rules/test-ns/test-gr'); + expect(group.path).toBe('/api/ruler/cortex-1/api/v1/rules/test-ns/test-gr'); expect(group.params).toMatchObject({ subtype: 'cortex' }); }); it('Should use /api/v1/rules endpoint with subtype = mimir parameter for config api version', () => { - // Arrange - const config: RulerDataSourceConfig = { - dataSourceName: 'Cortex v2', - apiVersion: 'config', - }; - - mocks.getDatasourceAPIUId.mockReturnValue('ds-uid'); - // Act - const builder = rulerUrlBuilder(config); + const builder = rulerUrlBuilder(mimirConfig); const rules = builder.rules(); const namespace = builder.namespace('test-ns'); const group = builder.namespaceGroup('test-ns', 'test-gr'); // Assert - expect(rules.path).toBe('/api/ruler/ds-uid/api/v1/rules'); + expect(rules.path).toBe('/api/ruler/mimir-1/api/v1/rules'); expect(rules.params).toMatchObject({ subtype: 'mimir' }); - expect(namespace.path).toBe('/api/ruler/ds-uid/api/v1/rules/test-ns'); + expect(namespace.path).toBe('/api/ruler/mimir-1/api/v1/rules/test-ns'); expect(namespace.params).toMatchObject({ subtype: 'mimir' }); - expect(group.path).toBe('/api/ruler/ds-uid/api/v1/rules/test-ns/test-gr'); + expect(group.path).toBe('/api/ruler/mimir-1/api/v1/rules/test-ns/test-gr'); expect(group.params).toMatchObject({ subtype: 'mimir' }); }); - it('Should append source=rules parameter when custom ruler enabled', () => { - // Arrange - const config: RulerDataSourceConfig = { - dataSourceName: 'Cortex v2', - apiVersion: 'config', - }; - - mocks.getDatasourceAPIUId.mockReturnValue('ds-uid'); - + it('Should append subtype parameter when custom ruler enabled', () => { // Act - const builder = rulerUrlBuilder(config); + const builder = rulerUrlBuilder(mimirConfig); const rules = builder.rules(); const namespace = builder.namespace('test-ns'); @@ -88,19 +83,52 @@ describe('rulerUrlBuilder', () => { }); it('Should append dashboard_uid and panel_id for rules endpoint when specified', () => { - // Arrange - const config: RulerDataSourceConfig = { - dataSourceName: 'Cortex v2', - apiVersion: 'config', - }; - - mocks.getDatasourceAPIUId.mockReturnValue('ds-uid'); - // Act - const builder = rulerUrlBuilder(config); + const builder = rulerUrlBuilder(mimirConfig); const rules = builder.rules({ dashboardUID: 'dashboard-uid', panelId: 1234 }); // Assert expect(rules.params).toMatchObject({ dashboard_uid: 'dashboard-uid', panel_id: '1234', subtype: 'mimir' }); }); + + describe('When slash in namespace or group', () => { + it('Should use QUERY_NAMESPACE and QUERY_GROUP path placeholders and include names in query string params', () => { + // Act + const builder = rulerUrlBuilder(mimirConfig); + + const namespace = builder.namespace('test/ns'); + const group = builder.namespaceGroup('test/ns', 'test/gr'); + + // Assert + expect(namespace.path).toBe('/api/ruler/mimir-1/api/v1/rules/QUERY_NAMESPACE'); + expect(namespace.params).toMatchObject({ subtype: 'mimir', namespace: 'test/ns' }); + + expect(group.path).toBe('/api/ruler/mimir-1/api/v1/rules/QUERY_NAMESPACE/QUERY_GROUP'); + expect(group.params).toMatchObject({ subtype: 'mimir', namespace: 'test/ns', group: 'test/gr' }); + }); + + it('Should use the tag replacement only when the slash is present', () => { + // Act + const builder = rulerUrlBuilder(mimirConfig); + + const group = builder.namespaceGroup('test-ns', 'test/gr'); + + // Assert + expect(group.path).toBe('/api/ruler/mimir-1/api/v1/rules/test-ns/QUERY_GROUP'); + expect(group.params).toMatchObject({ subtype: 'mimir', group: 'test/gr' }); + }); + + // GMA uses folderUIDs as namespaces and they should never contain slashes + it('Should only replace the group segment for Grafana-managed rules', () => { + // Act + const builder = rulerUrlBuilder(grafanaConfig); + + const group = builder.namespaceGroup('test/ns', 'test/gr'); + + // Assert + expect(group.path).toBe(`/api/ruler/grafana/api/v1/rules/${encodeURIComponent('test/ns')}/QUERY_GROUP`); + expect(group.params).toHaveProperty('group'); + expect(group.params).not.toHaveProperty('namespace'); + }); + }); }); diff --git a/public/app/features/alerting/unified/api/ruler.ts b/public/app/features/alerting/unified/api/ruler.ts index 6426b828db5..331d45154d0 100644 --- a/public/app/features/alerting/unified/api/ruler.ts +++ b/public/app/features/alerting/unified/api/ruler.ts @@ -5,10 +5,11 @@ import { FetchResponse, getBackendSrv } from '@grafana/runtime'; import { RulerDataSourceConfig } from 'app/types/unified-alerting'; import { PostableRulerRuleGroupDTO, RulerRuleGroupDTO, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; +import { checkForPathSeparator } from '../components/rule-editor/util'; import { RULER_NOT_SUPPORTED_MSG } from '../utils/constants'; import { getDatasourceAPIUid, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; -import { prepareRulesFilterQueryParams } from './prometheus'; +import { getRulesFilterSearchParams } from './prometheus'; interface ErrorResponseMessage { message?: string; @@ -20,34 +21,92 @@ export interface RulerRequestUrl { params?: Record; } -export function rulerUrlBuilder(rulerConfig: RulerDataSourceConfig) { - const grafanaServerPath = `/api/ruler/${getDatasourceAPIUid(rulerConfig.dataSourceName)}`; +const QUERY_NAMESPACE_TAG = 'QUERY_NAMESPACE'; +const QUERY_GROUP_TAG = 'QUERY_GROUP'; - const rulerPath = `${grafanaServerPath}/api/v1/rules`; - const rulerSearchParams = new URLSearchParams(); +export function rulerUrlBuilder(rulerConfig: RulerDataSourceConfig) { + const rulerPath = getRulerPath(rulerConfig); + const queryDetailsProvider = getQueryDetailsProvider(rulerConfig); - rulerSearchParams.set('subtype', rulerConfig.apiVersion === 'legacy' ? 'cortex' : 'mimir'); + const subtype = rulerConfig.apiVersion === 'legacy' ? 'cortex' : 'mimir'; return { - rules: (filter?: FetchRulerRulesFilter): RulerRequestUrl => { - const params = prepareRulesFilterQueryParams(rulerSearchParams, filter); + rules: (filter?: FetchRulerRulesFilter): RulerRequestUrl => ({ + path: rulerPath, + params: { subtype, ...getRulesFilterSearchParams(filter) }, + }), + + namespace: (namespace: string): RulerRequestUrl => { + // To handle slashes we need to convert namespace to a query parameter + const { namespace: finalNs, searchParams: nsParams } = queryDetailsProvider.namespace(namespace); return { - path: `${rulerPath}`, - params: params, + path: `${rulerPath}/${encodeURIComponent(finalNs)}`, + params: { subtype, ...nsParams }, }; }, - namespace: (namespace: string): RulerRequestUrl => ({ - path: `${rulerPath}/${encodeURIComponent(namespace)}`, - params: Object.fromEntries(rulerSearchParams), - }), - namespaceGroup: (namespaceUID: string, group: string): RulerRequestUrl => ({ - path: `${rulerPath}/${encodeURIComponent(namespaceUID)}/${encodeURIComponent(group)}`, - params: Object.fromEntries(rulerSearchParams), - }), + + namespaceGroup: (namespaceUID: string, group: string): RulerRequestUrl => { + const { namespace: finalNs, searchParams: nsParams } = queryDetailsProvider.namespace(namespaceUID); + const { group: finalGroup, searchParams: groupParams } = queryDetailsProvider.group(group); + + return { + path: `${rulerPath}/${encodeURIComponent(finalNs)}/${encodeURIComponent(finalGroup)}`, + params: { subtype, ...nsParams, ...groupParams }, + }; + }, + }; +} + +interface NamespaceUrlParams { + namespace: string; + searchParams: Record; +} + +interface GroupUrlParams { + group: string; + searchParams: Record; +} + +interface RulerQueryDetailsProvider { + namespace: (namespace: string) => NamespaceUrlParams; + group: (group: string) => GroupUrlParams; +} + +function getQueryDetailsProvider(rulerConfig: RulerDataSourceConfig): RulerQueryDetailsProvider { + const isGrafanaDatasource = rulerConfig.dataSourceName === GRAFANA_RULES_SOURCE_NAME; + + const groupParamRewrite = (group: string): GroupUrlParams => { + if (checkForPathSeparator(group) !== true) { + return { group: QUERY_GROUP_TAG, searchParams: { group } }; + } + return { group, searchParams: {} }; + }; + + // GMA uses folderUID as namespace identifiers so we need to rewrite them + if (isGrafanaDatasource) { + return { + namespace: (namespace: string) => ({ namespace, searchParams: {} }), + group: groupParamRewrite, + }; + } + + return { + namespace: (namespace: string): NamespaceUrlParams => { + if (checkForPathSeparator(namespace) !== true) { + return { namespace: QUERY_NAMESPACE_TAG, searchParams: { namespace } }; + } + return { namespace, searchParams: {} }; + }, + group: groupParamRewrite, }; } +function getRulerPath(rulerConfig: RulerDataSourceConfig) { + const grafanaServerPath = `/api/ruler/${getDatasourceAPIUid(rulerConfig.dataSourceName)}`; + return `${grafanaServerPath}/api/v1/rules`; +} + // upsert a rule group. use this to update rule export async function setRulerRuleGroup( rulerConfig: RulerDataSourceConfig, From 0258842f871e6d59928deda4120dcbdf5ef67602 Mon Sep 17 00:00:00 2001 From: Leonor Oliveira <9090754+leonorfmartins@users.noreply.github.com> Date: Tue, 13 Aug 2024 09:03:28 +0100 Subject: [PATCH 034/229] Use dw dynamic config (#91222) * Remove kubernetesPlaylists feature_toggle * Remove unified_storage_mode * Remove double import * Regenerate feature-toggles * Read from config instead from feature_toggle * cover scenario for when unified storage is not defined --- .../feature-toggles/index.md | 1 - .../src/types/featureToggles.gen.ts | 1 - packages/grafana-runtime/src/config.ts | 1 + pkg/api/playlist.go | 8 ++-- pkg/services/apiserver/README.md | 9 ++-- pkg/services/apiserver/config.go | 15 +++---- pkg/services/featuremgmt/registry.go | 8 ---- pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 -- pkg/services/featuremgmt/toggles_gen.json | 1 + pkg/setting/setting.go | 4 ++ pkg/tests/apis/playlist/playlist_test.go | 41 ++----------------- pkg/tests/testinfra/testinfra.go | 2 +- public/app/features/playlist/api.ts | 5 ++- 14 files changed, 31 insertions(+), 70 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 97781c967bb..31128abc74b 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -50,7 +50,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes | | `formatString` | Enable format string transformer | Yes | | `transformationsVariableSupport` | Allows using variables in transformations | Yes | -| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | Yes | | `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes | | `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes | | `managedPluginsInstall` | Install managed plugins directly from plugins catalog | Yes | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 20a0390eeb4..84290064f75 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -112,7 +112,6 @@ export interface FeatureToggles { disableClassicHTTPHistogram?: boolean; formatString?: boolean; transformationsVariableSupport?: boolean; - kubernetesPlaylists?: boolean; kubernetesSnapshots?: boolean; kubernetesDashboards?: boolean; datasourceQueryTypes?: boolean; diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index b2bc145eacf..5b0d48f5e1c 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -185,6 +185,7 @@ export class GrafanaBootConfig implements GrafanaConfig { cloudMigrationPollIntervalMs = 2000; reportingStaticContext?: Record; exploreDefaultTimeOffset = '1h'; + unifiedStorage: Map = new Map(); /** * Language used in Grafana's UI. This is after the user's preference (or deteceted locale) is resolved to one of diff --git a/pkg/api/playlist.go b/pkg/api/playlist.go index b395fad006e..d6d3b844a28 100644 --- a/pkg/api/playlist.go +++ b/pkg/api/playlist.go @@ -12,13 +12,12 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1" + playlistalpha1 "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1" "github.com/grafana/grafana/pkg/middleware" internalplaylist "github.com/grafana/grafana/pkg/registry/apis/playlist" grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/playlist" "github.com/grafana/grafana/pkg/util/errhttp" "github.com/grafana/grafana/pkg/web" @@ -27,7 +26,8 @@ import ( func (hs *HTTPServer) registerPlaylistAPI(apiRoute routing.RouteRegister) { // Register the actual handlers apiRoute.Group("/playlists", func(playlistRoute routing.RouteRegister) { - if hs.Features.IsEnabledGlobally(featuremgmt.FlagKubernetesPlaylists) { + unifiedStorageOptions := hs.Cfg.UnifiedStorage + if mode, ok := unifiedStorageOptions[playlistalpha1.GROUPRESOURCE]; ok && mode > 0 { // Use k8s client to implement legacy API handler := newPlaylistK8sHandler(hs) playlistRoute.Get("/", handler.searchPlaylists) @@ -330,7 +330,7 @@ type playlistK8sHandler struct { func newPlaylistK8sHandler(hs *HTTPServer) *playlistK8sHandler { return &playlistK8sHandler{ - gvr: v0alpha1.PlaylistResourceInfo.GroupVersionResource(), + gvr: playlistalpha1.PlaylistResourceInfo.GroupVersionResource(), namespacer: request.GetNamespaceMapper(hs.Cfg), clientConfigProvider: hs.clientConfigProvider, } diff --git a/pkg/services/apiserver/README.md b/pkg/services/apiserver/README.md index 7a65d16e9e3..80eaafe5ff2 100644 --- a/pkg/services/apiserver/README.md +++ b/pkg/services/apiserver/README.md @@ -4,7 +4,6 @@ ```ini [feature_toggles] -kubernetesPlaylists = true ``` Start Grafana: @@ -62,8 +61,10 @@ For kubectl to work, grafana needs to run over https. To simplify development, app_mode = development [feature_toggles] -grafanaAPIServerEnsureKubectlAccess = true -kubernetesPlaylists = true +grafanaAPIServerEnsureKubectlAccess = true + +[unified_storage] +playlists.playlist.grafana.app = 2 ``` This will create a development kubeconfig and start a parallel ssl listener. It can be registered by @@ -90,4 +91,4 @@ The folder structure aims to follow the patterns established in standard (https: * [pkg/apis](/pkg/apis) - where API resource types are defined. this is based on the structure of the [sample-apiserver](https://github.com/kubernetes/sample-apiserver/tree/master/pkg/apis) * [hack/update-codegen.sh](/hack#kubernetes-hack-alert) - this script is used to run [k8s codegen](https://github.com/kubernetes/code-generator/), which generates the code that is used by the API server to handle the types defined in `pkg/apis`. it is based on the [update-codegen.sh from sample-apiserver](https://github.com/kubernetes/sample-apiserver/blob/master/hack/update-codegen.sh) * [pkg/registry/apis](/pkg/registry/apis) - where all of the types in `pkg/apis` are registered with the API server by implementing the [builder](/pkg/services/apiserver/builder/common.go#L18) interface. this pattern is unique to grafana, and is needed to support using wire dependencies in legacy storage implementations. this is separated from `pkg/apis` to avoid issues with k8s codegen. -* [pkg/cmd/grafana/apiserver](/pkg/cmd/grafana/apiserver) - this is where the apiserver is configured for the `grafana apiserver` CLI command, which can be used to launch standalone API servers. this will eventually be merged with the config in `pkg/services/apiserver` to reduce duplication. \ No newline at end of file +* [pkg/cmd/grafana/apiserver](/pkg/cmd/grafana/apiserver) - this is where the apiserver is configured for the `grafana apiserver` CLI command, which can be used to launch standalone API servers. this will eventually be merged with the config in `pkg/services/apiserver` to reduce duplication. diff --git a/pkg/services/apiserver/config.go b/pkg/services/apiserver/config.go index dee64ca4848..5141702d041 100644 --- a/pkg/services/apiserver/config.go +++ b/pkg/services/apiserver/config.go @@ -7,7 +7,6 @@ import ( "strconv" playlist "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1" - grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/apiserver/options" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" @@ -55,14 +54,16 @@ func applyGrafanaConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles, o o.StorageOptions.StorageType = options.StorageType(apiserverCfg.Key("storage_type").MustString(string(options.StorageTypeLegacy))) o.StorageOptions.DataPath = apiserverCfg.Key("storage_path").MustString(filepath.Join(cfg.DataPath, "grafana-apiserver")) o.StorageOptions.Address = apiserverCfg.Key("address").MustString(o.StorageOptions.Address) - o.StorageOptions.DualWriterDesiredModes = map[string]grafanarest.DualWriterMode{ - // TODO: use the new config from HGAPI after https://github.com/grafana/hosted-grafana/pull/5707 - playlist.GROUPRESOURCE: 2, - } + + // unified storage modes + unifiedStorageCfg := cfg.UnifiedStorage + o.StorageOptions.DualWriterDesiredModes = unifiedStorageCfg // TODO: ensure backwards compatibility with production - // remove this after changing the unified_storage_mode key format in HGAPI - o.StorageOptions.DualWriterDesiredModes[playlist.RESOURCE+"."+playlist.GROUP] = o.StorageOptions.DualWriterDesiredModes[playlist.GROUPRESOURCE] + // remove this after changing the unified_storage key format in HGAPI + if _, ok := o.StorageOptions.DualWriterDesiredModes[playlist.RESOURCE+"."+playlist.GROUP]; ok { + o.StorageOptions.DualWriterDesiredModes[playlist.RESOURCE+"."+playlist.GROUP] = o.StorageOptions.DualWriterDesiredModes[playlist.GROUPRESOURCE] + } o.ExtraOptions.DevMode = features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerEnsureKubectlAccess) o.ExtraOptions.ExternalAddress = host diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 942bbe4a61a..70023e4e489 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -714,14 +714,6 @@ var ( Owner: grafanaDatavizSquad, Expression: "true", // Enabled by default }, - { - Name: "kubernetesPlaylists", - Description: "Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s", - Stage: FeatureStageGeneralAvailability, - Owner: grafanaAppPlatformSquad, - Expression: "true", - RequiresRestart: true, // changes the API routing - }, { Name: "kubernetesSnapshots", Description: "Routes snapshot requests from /api to the /apis endpoint", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 415fdd789ca..edd8800cd30 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -93,7 +93,6 @@ enableNativeHTTPHistogram,experimental,@grafana/grafana-backend-services-squad,f disableClassicHTTPHistogram,experimental,@grafana/grafana-backend-services-squad,false,true,false formatString,GA,@grafana/dataviz-squad,false,false,true transformationsVariableSupport,GA,@grafana/dataviz-squad,false,false,true -kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true datasourceQueryTypes,experimental,@grafana/grafana-app-platform-squad,false,true,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index ac06c13d7e2..84381836572 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -383,10 +383,6 @@ const ( // Allows using variables in transformations FlagTransformationsVariableSupport = "transformationsVariableSupport" - // FlagKubernetesPlaylists - // Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s - FlagKubernetesPlaylists = "kubernetesPlaylists" - // FlagKubernetesSnapshots // Routes snapshot requests from /api to the /apis endpoint FlagKubernetesSnapshots = "kubernetesSnapshots" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index ede7702b5a5..931fa9b8524 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1438,6 +1438,7 @@ "name": "kubernetesPlaylists", "resourceVersion": "1720021873452", "creationTimestamp": "2023-10-05T19:00:36Z", + "deletionTimestamp": "2024-07-30T19:34:12Z", "annotations": { "grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC" } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index ce111891dd1..1d1b1f7e872 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -29,6 +29,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" "github.com/grafana/grafana/pkg/apimachinery/identity" + grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util/osutil" @@ -520,6 +521,9 @@ type Cfg struct { //Short Links ShortLinkExpiration int + + // Unified Storage + UnifiedStorage map[string]grafanarest.DualWriterMode } // AddChangePasswordLink returns if login form is disabled or not since diff --git a/pkg/tests/apis/playlist/playlist_test.go b/pkg/tests/apis/playlist/playlist_test.go index d42b63d5149..cd7f02037a5 100644 --- a/pkg/tests/apis/playlist/playlist_test.go +++ b/pkg/tests/apis/playlist/playlist_test.go @@ -77,24 +77,11 @@ func TestIntegrationPlaylist(t *testing.T) { ]`, disco) }) - t.Run("with k8s api flag", func(t *testing.T) { - doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ - AppModeProduction: true, // do not start extra port 6443 - DisableAnonymous: true, - EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesPlaylists, // <<< The change we are testing! - }, - })) - }) - t.Run("with dual write (file, mode 0)", func(t *testing.T) { doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ AppModeProduction: true, DisableAnonymous: true, APIServerStorageType: "file", // write the files to disk - EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written - }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode0, }, @@ -106,9 +93,6 @@ func TestIntegrationPlaylist(t *testing.T) { AppModeProduction: true, DisableAnonymous: true, APIServerStorageType: "file", // write the files to disk - EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written - }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode1, }, @@ -120,9 +104,6 @@ func TestIntegrationPlaylist(t *testing.T) { AppModeProduction: true, DisableAnonymous: true, APIServerStorageType: "file", // write the files to disk - EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written - }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode2, }, @@ -134,9 +115,6 @@ func TestIntegrationPlaylist(t *testing.T) { AppModeProduction: true, DisableAnonymous: true, APIServerStorageType: "file", // write the files to disk - EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written - }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode3, }, @@ -150,7 +128,6 @@ func TestIntegrationPlaylist(t *testing.T) { APIServerStorageType: "unified", // use the entity api tables EnableFeatureToggles: []string{ featuremgmt.FlagUnifiedStorage, - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode0, @@ -165,7 +142,6 @@ func TestIntegrationPlaylist(t *testing.T) { APIServerStorageType: "unified", // use the entity api tables EnableFeatureToggles: []string{ featuremgmt.FlagUnifiedStorage, - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode1, @@ -180,7 +156,6 @@ func TestIntegrationPlaylist(t *testing.T) { APIServerStorageType: "unified", // use the entity api tables EnableFeatureToggles: []string{ featuremgmt.FlagUnifiedStorage, - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode2, @@ -195,7 +170,6 @@ func TestIntegrationPlaylist(t *testing.T) { APIServerStorageType: "unified", // use the entity api tables EnableFeatureToggles: []string{ featuremgmt.FlagUnifiedStorage, - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode3, @@ -211,9 +185,6 @@ func TestIntegrationPlaylist(t *testing.T) { AppModeProduction: true, DisableAnonymous: true, APIServerStorageType: "etcd", // requires etcd running on localhost:2379 - EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written - }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode0, }, @@ -238,9 +209,7 @@ func TestIntegrationPlaylist(t *testing.T) { AppModeProduction: true, DisableAnonymous: true, APIServerStorageType: "etcd", // requires etcd running on localhost:2379 - EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written - }, + EnableFeatureToggles: []string{}, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode1, }, @@ -265,9 +234,7 @@ func TestIntegrationPlaylist(t *testing.T) { AppModeProduction: true, DisableAnonymous: true, APIServerStorageType: "etcd", // requires etcd running on localhost:2379 - EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written - }, + EnableFeatureToggles: []string{}, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode2, }, @@ -292,9 +259,7 @@ func TestIntegrationPlaylist(t *testing.T) { AppModeProduction: true, DisableAnonymous: true, APIServerStorageType: "etcd", // requires etcd running on localhost:2379 - EnableFeatureToggles: []string{ - featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written - }, + EnableFeatureToggles: []string{}, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ playlistv0alpha1.GROUPRESOURCE: grafanarest.Mode3, }, diff --git a/pkg/tests/testinfra/testinfra.go b/pkg/tests/testinfra/testinfra.go index 3da3265607a..f22055f2d64 100644 --- a/pkg/tests/testinfra/testinfra.go +++ b/pkg/tests/testinfra/testinfra.go @@ -387,7 +387,7 @@ func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) { } if o.DualWriterDesiredModes != nil { - unifiedStorageMode, err := getOrCreateSection("unified_storage_mode") + unifiedStorageMode, err := getOrCreateSection("unified_storage") require.NoError(t, err) for k, v := range o.DualWriterDesiredModes { _, err = unifiedStorageMode.NewKey(k, fmt.Sprint(v)) diff --git a/public/app/features/playlist/api.ts b/public/app/features/playlist/api.ts index dd778f8903d..4cc9559dee5 100644 --- a/public/app/features/playlist/api.ts +++ b/public/app/features/playlist/api.ts @@ -209,5 +209,8 @@ export function searchPlaylists(playlists: Playlist[], query?: string): Playlist } export function getPlaylistAPI() { - return config.featureToggles.kubernetesPlaylists ? new K8sAPI() : new LegacyAPI(); + if (config.unifiedStorage) { + return config.unifiedStorage.has('playlist.grafana.app/playlists') ? new K8sAPI() : new LegacyAPI(); + } + return new LegacyAPI(); } From 8bcd9c2594ba51ada3af20e0049299f5504759b8 Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Tue, 13 Aug 2024 10:18:28 +0200 Subject: [PATCH 035/229] Identity: Remove typed id (#91801) * Refactor identity struct to store type in separate field * Update ResolveIdentity to take string representation of typedID * Add IsIdentityType to requester interface * Use IsIdentityType from interface * Remove usage of TypedID * Remote typedID struct * fix GetInternalID --- pkg/api/common_test.go | 4 +- pkg/api/dashboard.go | 4 +- pkg/api/folder.go | 4 +- pkg/api/index.go | 2 +- pkg/api/login_test.go | 4 +- pkg/api/org.go | 5 +- pkg/api/org_test.go | 5 +- pkg/api/user.go | 19 ++-- pkg/api/user_token.go | 9 +- pkg/apimachinery/identity/requester.go | 47 +++++----- pkg/apimachinery/identity/static.go | 9 +- pkg/apimachinery/identity/typed_id.go | 87 ++----------------- pkg/middleware/auth_test.go | 14 +-- pkg/middleware/quota_test.go | 4 +- .../apis/dashboard/legacy/sql_dashboards.go | 13 +-- pkg/registry/apis/dashboard/sub_dto.go | 2 +- pkg/services/accesscontrol/accesscontrol.go | 14 +-- pkg/services/accesscontrol/acimpl/service.go | 8 +- pkg/services/accesscontrol/api/api.go | 13 +-- .../accesscontrol/authorize_in_org_test.go | 5 +- .../accesscontrol/database/database.go | 2 +- pkg/services/anonymous/anonimpl/client.go | 8 +- .../anonymous/anonimpl/client_test.go | 20 +++-- pkg/services/auth/idimpl/service.go | 4 +- pkg/services/auth/idimpl/service_test.go | 15 ++-- pkg/services/authn/authn.go | 8 +- pkg/services/authn/authnimpl/service.go | 49 +++++++---- pkg/services/authn/authnimpl/service_test.go | 54 ++++++------ .../authn/authnimpl/sync/oauth_token_sync.go | 8 +- .../authnimpl/sync/oauth_token_sync_test.go | 11 +-- pkg/services/authn/authnimpl/sync/org_sync.go | 16 ++-- .../authn/authnimpl/sync/org_sync_test.go | 23 ++--- .../authn/authnimpl/sync/rbac_sync.go | 4 +- .../authn/authnimpl/sync/rbac_sync_test.go | 33 ++++--- .../authn/authnimpl/sync/user_sync.go | 29 +++---- .../authn/authnimpl/sync/user_sync_test.go | 44 ++++++---- pkg/services/authn/authntest/fake.go | 2 +- pkg/services/authn/authntest/mock.go | 8 +- pkg/services/authn/clients/api_key.go | 21 +++-- pkg/services/authn/clients/api_key_test.go | 60 ++++++++----- pkg/services/authn/clients/basic_test.go | 6 +- pkg/services/authn/clients/ext_jwt.go | 27 +++--- pkg/services/authn/clients/ext_jwt_test.go | 26 +++--- pkg/services/authn/clients/grafana.go | 5 +- pkg/services/authn/clients/grafana_test.go | 5 +- pkg/services/authn/clients/oauth_test.go | 2 +- pkg/services/authn/clients/password_test.go | 10 +-- pkg/services/authn/clients/proxy.go | 13 +-- pkg/services/authn/clients/proxy_test.go | 8 +- pkg/services/authn/clients/render.go | 8 +- pkg/services/authn/clients/render_test.go | 8 +- pkg/services/authn/clients/session.go | 5 +- pkg/services/authn/clients/session_test.go | 15 ++-- pkg/services/authn/identity.go | 50 ++++++----- pkg/services/authz/server.go | 16 ++-- pkg/services/contexthandler/contexthandler.go | 11 ++- .../contexthandler/contexthandler_test.go | 9 +- pkg/services/dashboards/database/database.go | 2 +- .../dashboards/service/dashboard_service.go | 12 +-- .../dashboardsnapshots/database/database.go | 5 +- pkg/services/folder/folderimpl/sqlstore.go | 4 +- pkg/services/live/live.go | 3 +- pkg/services/ngalert/api/api_ruler.go | 10 ++- pkg/services/oauthtoken/oauth_token.go | 8 +- pkg/services/oauthtoken/oauth_token_test.go | 26 +++--- .../user_header_middleware.go | 3 +- pkg/services/serviceaccounts/api/api.go | 5 +- pkg/services/team/teamapi/team.go | 12 +-- pkg/services/user/identity.go | 20 ++--- pkg/services/user/userimpl/verifier.go | 4 +- .../unified/resource/grpc/authenticator.go | 16 ++-- pkg/util/proxyutil/proxyutil.go | 2 +- 72 files changed, 528 insertions(+), 519 deletions(-) diff --git a/pkg/api/common_test.go b/pkg/api/common_test.go index bc1b44a7a87..f94fb4dd29d 100644 --- a/pkg/api/common_test.go +++ b/pkg/api/common_test.go @@ -11,12 +11,12 @@ import ( "path/filepath" "testing" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/fs" "github.com/grafana/grafana/pkg/infra/tracing" @@ -190,7 +190,7 @@ func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHa return contexthandler.ProvideService( cfg, tracing.InitializeTracerForTest(), - &authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: identity.AnonymousTypedID, SessionToken: &usertoken.UserToken{}}}, + &authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: "0", Type: claims.TypeAnonymous, SessionToken: &usertoken.UserToken{}}}, ) } diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 97a8693fb22..df13025462d 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -44,11 +44,11 @@ func (hs *HTTPServer) isDashboardStarredByUser(c *contextmodel.ReqContext, dashI return false, nil } - if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { + if !c.SignedInUser.IsIdentityType(claims.TypeUser) { return false, nil } - userID, err := identity.UserIdentifier(c.SignedInUser.GetID()) + userID, err := c.SignedInUser.GetInternalID() if err != nil { return false, err } diff --git a/pkg/api/folder.go b/pkg/api/folder.go index 3b379cd5291..889168ccdd9 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -195,8 +195,8 @@ func (hs *HTTPServer) setDefaultFolderPermissions(ctx context.Context, orgID int var permissions []accesscontrol.SetResourcePermissionCommand - if identity.IsIdentityType(user.GetID(), claims.TypeUser) { - userID, err := identity.UserIdentifier(user.GetID()) + if user.IsIdentityType(claims.TypeUser) { + userID, err := user.GetInternalID() if err != nil { return err } diff --git a/pkg/api/index.go b/pkg/api/index.go index 8f1eebad990..69aef128295 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -171,7 +171,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV func (hs *HTTPServer) buildUserAnalyticsSettings(c *contextmodel.ReqContext) dtos.AnalyticsSettings { // Anonymous users do not have an email or auth info - if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { + if !c.SignedInUser.IsIdentityType(claims.TypeUser) { return dtos.AnalyticsSettings{Identifier: "@" + hs.Cfg.AppURL} } diff --git a/pkg/api/login_test.go b/pkg/api/login_test.go index c442b35cb3e..4e4fbcad787 100644 --- a/pkg/api/login_test.go +++ b/pkg/api/login_test.go @@ -12,13 +12,13 @@ import ( "strings" "testing" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/login/social" @@ -332,7 +332,7 @@ func TestLoginPostRedirect(t *testing.T) { HooksService: &hooks.HooksService{}, License: &licensing.OSSLicensingService{}, authnService: &authntest.FakeService{ - ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:42"), SessionToken: &usertoken.UserToken{}}, + ExpectedIdentity: &authn.Identity{ID: "42", Type: claims.TypeUser, SessionToken: &usertoken.UserToken{}}, }, AuthTokenService: authtest.NewFakeUserAuthTokenService(), Features: featuremgmt.WithFeatures(), diff --git a/pkg/api/org.go b/pkg/api/org.go index 88181f7e48a..882ebfb866d 100644 --- a/pkg/api/org.go +++ b/pkg/api/org.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/metrics" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/org" @@ -133,11 +132,11 @@ func (hs *HTTPServer) CreateOrg(c *contextmodel.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "bad request data", err) } - if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { + if !c.SignedInUser.IsIdentityType(claims.TypeUser) { return response.Error(http.StatusForbidden, "Only users can create organizations", nil) } - userID, err := identity.UserIdentifier(c.SignedInUser.GetID()) + userID, err := c.SignedInUser.GetInternalID() if err != nil { return response.Error(http.StatusInternalServerError, "Failed to parse user id", err) } diff --git a/pkg/api/org_test.go b/pkg/api/org_test.go index 246b7cd303c..77042fb896c 100644 --- a/pkg/api/org_test.go +++ b/pkg/api/org_test.go @@ -6,10 +6,10 @@ import ( "strings" "testing" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/authn" @@ -267,7 +267,8 @@ func TestAPIEndpoint_GetOrg(t *testing.T) { for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { expectedIdentity := &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, OrgID: 1, Permissions: map[int64]map[string][]string{ 0: accesscontrol.GroupScopesByActionContext(context.Background(), tt.permissions), diff --git a/pkg/api/user.go b/pkg/api/user.go index 6179f71f0b2..3f66caeef38 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/apimachinery/identity" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -32,18 +31,18 @@ import ( // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) GetSignedInUser(c *contextmodel.ReqContext) response.Response { - if !identity.IsIdentityType(c.GetID(), claims.TypeUser) { + if !c.IsIdentityType(claims.TypeUser) { return response.JSON(http.StatusOK, user.UserProfileDTO{ IsGrafanaAdmin: c.SignedInUser.GetIsGrafanaAdmin(), OrgID: c.SignedInUser.GetOrgID(), - UID: c.SignedInUser.GetID().String(), + UID: c.SignedInUser.GetID(), Name: c.SignedInUser.NameOrFallback(), Email: c.SignedInUser.GetEmail(), Login: c.SignedInUser.GetLogin(), }) } - userID, err := identity.UserIdentifier(c.GetID()) + userID, err := c.GetInternalID() if err != nil { return response.Error(http.StatusInternalServerError, "Failed to parse user id", err) } @@ -278,7 +277,7 @@ func (hs *HTTPServer) handleUpdateUser(ctx context.Context, cmd user.UpdateUserC } func (hs *HTTPServer) StartEmailVerificaton(c *contextmodel.ReqContext) response.Response { - if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { + if !c.SignedInUser.IsIdentityType(claims.TypeUser) { return response.Error(http.StatusBadRequest, "Only users can verify their email", nil) } @@ -287,7 +286,7 @@ func (hs *HTTPServer) StartEmailVerificaton(c *contextmodel.ReqContext) response return response.Respond(http.StatusNotModified, nil) } - userID, err := identity.UserIdentifier(c.SignedInUser.GetID()) + userID, err := c.SignedInUser.GetInternalID() if err != nil { return response.Error(http.StatusInternalServerError, "Got invalid user id", err) } @@ -505,12 +504,12 @@ func (hs *HTTPServer) ChangeActiveOrgAndRedirectToHome(c *contextmodel.ReqContex return } - if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { + if !c.SignedInUser.IsIdentityType(claims.TypeUser) { c.JsonApiErr(http.StatusForbidden, "Endpoint only available for users", nil) return } - userID, err := identity.UserIdentifier(c.SignedInUser.GetID()) + userID, err := c.SignedInUser.GetInternalID() if err != nil { c.JsonApiErr(http.StatusInternalServerError, "Failed to parse user id", err) return @@ -630,11 +629,11 @@ func (hs *HTTPServer) ClearHelpFlags(c *contextmodel.ReqContext) response.Respon } func getUserID(c *contextmodel.ReqContext) (int64, *response.NormalResponse) { - if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { + if !c.SignedInUser.IsIdentityType(claims.TypeUser) { return 0, response.Error(http.StatusForbidden, "Endpoint only available for users", nil) } - userID, err := identity.UserIdentifier(c.SignedInUser.GetID()) + userID, err := c.SignedInUser.GetInternalID() if err != nil { return 0, response.Error(http.StatusInternalServerError, "Failed to parse user id", err) } diff --git a/pkg/api/user_token.go b/pkg/api/user_token.go index 41ba01e7a11..166412292fe 100644 --- a/pkg/api/user_token.go +++ b/pkg/api/user_token.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/network" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn" @@ -33,11 +32,11 @@ import ( // 403: forbiddenError // 500: internalServerError func (hs *HTTPServer) GetUserAuthTokens(c *contextmodel.ReqContext) response.Response { - if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { + if !c.SignedInUser.IsIdentityType(claims.TypeUser) { return response.Error(http.StatusForbidden, "entity not allowed to get tokens", nil) } - userID, err := identity.UserIdentifier(c.SignedInUser.GetID()) + userID, err := c.SignedInUser.GetInternalID() if err != nil { return response.Error(http.StatusInternalServerError, "failed to parse user id", err) } @@ -63,11 +62,11 @@ func (hs *HTTPServer) RevokeUserAuthToken(c *contextmodel.ReqContext) response.R return response.Error(http.StatusBadRequest, "bad request data", err) } - if !identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { + if !c.SignedInUser.IsIdentityType(claims.TypeUser) { return response.Error(http.StatusForbidden, "entity not allowed to revoke tokens", nil) } - userID, err := identity.UserIdentifier(c.SignedInUser.GetID()) + userID, err := c.SignedInUser.GetInternalID() if err != nil { return response.Error(http.StatusInternalServerError, "failed to parse user id", err) } diff --git a/pkg/apimachinery/identity/requester.go b/pkg/apimachinery/identity/requester.go index ff1e248368d..318fc0befed 100644 --- a/pkg/apimachinery/identity/requester.go +++ b/pkg/apimachinery/identity/requester.go @@ -15,13 +15,15 @@ type Requester interface { // GetIdentityType returns the type for the requester GetIdentityType() claims.IdentityType + // IsIdentityType returns true if identity type for requester matches any expected identity type + IsIdentityType(expected ...claims.IdentityType) bool // GetRawIdentifier returns only the identifier part of the UID, excluding the type GetRawIdentifier() string - // Deprecated: use GetUID instead + // GetInternalID returns only the identifier part of the ID, excluding the type GetInternalID() (int64, error) // GetID returns namespaced internalID for the entity // Deprecated: use GetUID instead - GetID() TypedID + GetID() string // GetDisplayName returns the display name of the active entity. // The display name is the name if it is set, otherwise the login or email. GetDisplayName() string @@ -81,35 +83,40 @@ type Requester interface { // IntIdentifier converts a typeID to an int64. // Applicable for users, service accounts, api keys and renderer service. // Errors if the identifier is not initialized or if type is not recognized. -func IntIdentifier(typedID TypedID) (int64, error) { - if claims.IsIdentityType(typedID.t, claims.TypeUser, claims.TypeAPIKey, claims.TypeServiceAccount, claims.TypeRenderService) { - id, err := strconv.ParseInt(typedID.ID(), 10, 64) - if err != nil { - return 0, fmt.Errorf("unrecognized format for valid type %s: %w", typedID.Type(), err) - } - - if id < 1 { - return 0, ErrIdentifierNotInitialized - } - - return id, nil +func IntIdentifier(typedID string) (int64, error) { + typ, id, err := ParseTypeAndID(typedID) + if err != nil { + return 0, err } - return 0, ErrNotIntIdentifier + return intIdentifier(typ, id, claims.TypeUser, claims.TypeAPIKey, claims.TypeServiceAccount, claims.TypeRenderService) } // UserIdentifier converts a typeID to an int64. // Errors if the identifier is not initialized or if namespace is not recognized. // Returns 0 if the type is not user or service account -func UserIdentifier(typedID TypedID) (int64, error) { - userID, err := IntIdentifier(typedID) +func UserIdentifier(typedID string) (int64, error) { + typ, id, err := ParseTypeAndID(typedID) if err != nil { return 0, err } - if claims.IsIdentityType(typedID.t, claims.TypeUser, claims.TypeServiceAccount) { - return userID, nil + return intIdentifier(typ, id, claims.TypeUser, claims.TypeServiceAccount) +} + +func intIdentifier(typ claims.IdentityType, id string, expected ...claims.IdentityType) (int64, error) { + if claims.IsIdentityType(typ, expected...) { + id, err := strconv.ParseInt(id, 10, 64) + if err != nil { + return 0, fmt.Errorf("unrecognized format for valid type %s: %w", typ, err) + } + + if id < 1 { + return 0, ErrIdentifierNotInitialized + } + + return id, nil } - return 0, ErrInvalidIDType + return 0, ErrNotIntIdentifier } diff --git a/pkg/apimachinery/identity/static.go b/pkg/apimachinery/identity/static.go index 5d4f25e9a8a..443c8fb0a61 100644 --- a/pkg/apimachinery/identity/static.go +++ b/pkg/apimachinery/identity/static.go @@ -69,6 +69,11 @@ func (u *StaticRequester) GetIdentityType() claims.IdentityType { return u.Type } +// IsIdentityType implements Requester. +func (u *StaticRequester) IsIdentityType(expected ...claims.IdentityType) bool { + return claims.IsIdentityType(u.GetIdentityType(), expected...) +} + // GetExtra implements Requester. func (u *StaticRequester) GetExtra() map[string][]string { return map[string][]string{} @@ -158,8 +163,8 @@ func (u *StaticRequester) HasUniqueId() bool { return u.UserID > 0 } -// GetID returns namespaced id for the entity -func (u *StaticRequester) GetID() TypedID { +// GetID returns typed id for the entity +func (u *StaticRequester) GetID() string { return NewTypedIDString(u.Type, fmt.Sprintf("%d", u.UserID)) } diff --git a/pkg/apimachinery/identity/typed_id.go b/pkg/apimachinery/identity/typed_id.go index a2e81179396..753975d2411 100644 --- a/pkg/apimachinery/identity/typed_id.go +++ b/pkg/apimachinery/identity/typed_id.go @@ -2,101 +2,30 @@ package identity import ( "fmt" - "strconv" "strings" "github.com/grafana/authlib/claims" ) -// IsIdentityType returns true if typedID matches any expected identity type -func IsIdentityType(typedID TypedID, expected ...claims.IdentityType) bool { - for _, e := range expected { - if typedID.Type() == e { - return true - } - } - - return false -} - -var AnonymousTypedID = NewTypedID(claims.TypeAnonymous, 0) - -func ParseTypedID(str string) (TypedID, error) { - var typeID TypedID - +func ParseTypeAndID(str string) (claims.IdentityType, string, error) { parts := strings.Split(str, ":") if len(parts) != 2 { - return typeID, ErrInvalidTypedID.Errorf("expected typed id to have 2 parts") + return "", "", ErrInvalidTypedID.Errorf("expected typed id to have 2 parts") } t, err := claims.ParseType(parts[0]) if err != nil { - return typeID, err + return "", "", err } - typeID.id = parts[1] - typeID.t = t - - return typeID, nil + return t, parts[1], nil } -// MustParseTypedID parses namespace id, it will panic if it fails to do so. -// Suitable to use in tests or when we can guarantee that we pass a correct format. -func MustParseTypedID(str string) TypedID { - typeID, err := ParseTypedID(str) - if err != nil { - panic(err) - } - return typeID -} - -func NewTypedID(t claims.IdentityType, id int64) TypedID { - return TypedID{ - id: strconv.FormatInt(id, 10), - t: t, - } +func NewTypedID(t claims.IdentityType, id int64) string { + return fmt.Sprintf("%s:%d", t, id) } // NewTypedIDString creates a new TypedID with a string id -func NewTypedIDString(t claims.IdentityType, id string) TypedID { - return TypedID{ - id: id, - t: t, - } -} - -// FIXME: use this instead of encoded string through the codebase -type TypedID struct { - id string - t claims.IdentityType -} - -func (ni TypedID) ID() string { - return ni.id -} - -// UserID will try to parse and int64 identifier if namespace is either user or service-account. -// For all other namespaces '0' will be returned. -func (ni TypedID) UserID() (int64, error) { - if ni.IsType(claims.TypeUser, claims.TypeServiceAccount) { - return ni.ParseInt() - } - return 0, nil -} - -// ParseInt will try to parse the id as an int64 identifier. -func (ni TypedID) ParseInt() (int64, error) { - return strconv.ParseInt(ni.id, 10, 64) -} - -func (ni TypedID) Type() claims.IdentityType { - return ni.t -} - -func (ni TypedID) IsType(expected ...claims.IdentityType) bool { - return IsIdentityType(ni, expected...) -} - -func (ni TypedID) String() string { - return fmt.Sprintf("%s:%s", ni.t, ni.id) +func NewTypedIDString(t claims.IdentityType, id string) string { + return fmt.Sprintf("%s:%s", t, id) } diff --git a/pkg/middleware/auth_test.go b/pkg/middleware/auth_test.go index ee75387d963..9af27a76c98 100644 --- a/pkg/middleware/auth_test.go +++ b/pkg/middleware/auth_test.go @@ -7,10 +7,10 @@ import ( "net/http/httptest" "testing" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log/logtest" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" @@ -63,7 +63,7 @@ func TestAuth_Middleware(t *testing.T) { desc: "ReqSignedIn should return 200 for anonymous user", path: "/api/secure", authMiddleware: ReqSignedIn, - identity: &authn.Identity{ID: identity.AnonymousTypedID}, + identity: &authn.Identity{Type: claims.TypeAnonymous}, expecedReached: true, expectedCode: http.StatusOK, }, @@ -71,7 +71,7 @@ func TestAuth_Middleware(t *testing.T) { desc: "ReqSignedIn should return redirect anonymous user with forceLogin query string", path: "/secure?forceLogin=true", authMiddleware: ReqSignedIn, - identity: &authn.Identity{ID: identity.AnonymousTypedID}, + identity: &authn.Identity{Type: claims.TypeAnonymous}, expecedReached: false, expectedCode: http.StatusFound, }, @@ -79,7 +79,7 @@ func TestAuth_Middleware(t *testing.T) { desc: "ReqSignedIn should return redirect anonymous user when orgId in query string is different from currently used", path: "/secure?orgId=2", authMiddleware: ReqSignedIn, - identity: &authn.Identity{ID: identity.AnonymousTypedID, OrgID: 1}, + identity: &authn.Identity{Type: claims.TypeAnonymous}, expecedReached: false, expectedCode: http.StatusFound, }, @@ -87,7 +87,7 @@ func TestAuth_Middleware(t *testing.T) { desc: "ReqSignedInNoAnonymous should return 401 for anonymous user", path: "/api/secure", authMiddleware: ReqSignedInNoAnonymous, - identity: &authn.Identity{ID: identity.AnonymousTypedID}, + identity: &authn.Identity{Type: claims.TypeAnonymous}, expecedReached: false, expectedCode: http.StatusUnauthorized, }, @@ -95,7 +95,7 @@ func TestAuth_Middleware(t *testing.T) { desc: "ReqSignedInNoAnonymous should return 200 for authenticated user", path: "/api/secure", authMiddleware: ReqSignedInNoAnonymous, - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser}, expecedReached: true, expectedCode: http.StatusOK, }, @@ -103,7 +103,7 @@ func TestAuth_Middleware(t *testing.T) { desc: "snapshot public mode disabled should return 200 for authenticated user", path: "/api/secure", authMiddleware: SnapshotPublicModeOrSignedIn(&setting.Cfg{SnapshotPublicMode: false}), - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser}, expecedReached: true, expectedCode: http.StatusOK, }, diff --git a/pkg/middleware/quota_test.go b/pkg/middleware/quota_test.go index 94b6ee3e0e3..8a1f15a4118 100644 --- a/pkg/middleware/quota_test.go +++ b/pkg/middleware/quota_test.go @@ -3,9 +3,9 @@ package middleware import ( "testing" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/quota/quotatest" @@ -53,7 +53,7 @@ func TestMiddlewareQuota(t *testing.T) { t.Run("with user logged in", func(t *testing.T) { setUp := func(sc *scenarioContext) { - sc.withIdentity(&authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{UserId: 12}}) + sc.withIdentity(&authn.Identity{ID: "1", Type: claims.TypeUser, SessionToken: &auth.UserToken{UserId: 12}}) } middlewareScenario(t, "global datasource quota reached", func(t *testing.T, sc *scenarioContext) { diff --git a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go index 85426711c63..8a17e906f3f 100644 --- a/pkg/registry/apis/dashboard/legacy/sql_dashboards.go +++ b/pkg/registry/apis/dashboard/legacy/sql_dashboards.go @@ -339,10 +339,10 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) { func getUserID(v sql.NullString, id sql.NullInt64) string { if v.Valid && v.String != "" { - return identity.NewTypedIDString(claims.TypeUser, v.String).String() + return identity.NewTypedIDString(claims.TypeUser, v.String) } if id.Valid && id.Int64 == -1 { - return identity.NewTypedIDString(claims.TypeProvisioning, "").String() + return identity.NewTypedIDString(claims.TypeProvisioning, "") } return "" } @@ -395,9 +395,12 @@ func (a *dashboardSqlAccess) SaveDashboard(ctx context.Context, orgId int64, das dash.Spec.Remove("uid") } - userID, err := user.GetID().UserID() - if err != nil { - return nil, false, err + var userID int64 + if user.IsIdentityType(claims.TypeUser) { + userID, err = user.GetInternalID() + if err != nil { + return nil, false, err + } } meta, err := utils.MetaAccessor(dash) diff --git a/pkg/registry/apis/dashboard/sub_dto.go b/pkg/registry/apis/dashboard/sub_dto.go index 7e0cd3a60a1..b71374d8178 100644 --- a/pkg/registry/apis/dashboard/sub_dto.go +++ b/pkg/registry/apis/dashboard/sub_dto.go @@ -87,7 +87,7 @@ func (r *DTOConnector) Connect(ctx context.Context, name string, opts runtime.Ob access.CanSave, _ = guardian.CanSave() access.CanAdmin, _ = guardian.CanAdmin() access.CanDelete, _ = guardian.CanDelete() - access.CanStar = user.GetID().Type() == claims.TypeUser // not anon + access.CanStar = user.IsIdentityType(claims.TypeUser) access.AnnotationsPermissions = &dashboard.AnnotationPermission{} r.getAnnotationPermissionsByScope(ctx, user, &access.AnnotationsPermissions.Dashboard, accesscontrol.ScopeAnnotationsTypeDashboard) diff --git a/pkg/services/accesscontrol/accesscontrol.go b/pkg/services/accesscontrol/accesscontrol.go index a4a48dbb71f..a3fea676c1c 100644 --- a/pkg/services/accesscontrol/accesscontrol.go +++ b/pkg/services/accesscontrol/accesscontrol.go @@ -3,6 +3,7 @@ package accesscontrol import ( "context" "fmt" + "strconv" "strings" "go.opentelemetry.io/otel" @@ -84,8 +85,8 @@ type SearchOptions struct { Action string ActionSets []string Scope string - TypedID identity.TypedID // ID of the identity (ex: user:3, service-account:4) - wildcards Wildcards // private field computed based on the Scope + TypedID string // ID of the identity (ex: user:3, service-account:4) + wildcards Wildcards // private field computed based on the Scope RolePrefixes []string } @@ -105,17 +106,16 @@ func (s *SearchOptions) Wildcards() []string { } func (s *SearchOptions) ComputeUserID() (int64, error) { - id, err := s.TypedID.ParseInt() + typ, id, err := identity.ParseTypeAndID(s.TypedID) if err != nil { return 0, err } - // Validate namespace type is user or service account - if s.TypedID.Type() != claims.TypeUser && s.TypedID.Type() != claims.TypeServiceAccount { - return 0, fmt.Errorf("invalid type: %s", s.TypedID.Type()) + if !claims.IsIdentityType(typ, claims.TypeUser, claims.TypeServiceAccount) { + return 0, fmt.Errorf("invalid type: %s", typ) } - return id, nil + return strconv.ParseInt(id, 10, 64) } type SyncUserRolesCommand struct { diff --git a/pkg/services/accesscontrol/acimpl/service.go b/pkg/services/accesscontrol/acimpl/service.go index 07e9168bf6e..875ec43e234 100644 --- a/pkg/services/accesscontrol/acimpl/service.go +++ b/pkg/services/accesscontrol/acimpl/service.go @@ -212,9 +212,9 @@ func (s *Service) getUserDirectPermissions(ctx context.Context, user identity.Re defer span.End() var userID int64 - if identity.IsIdentityType(user.GetID(), claims.TypeUser, claims.TypeServiceAccount) { + if user.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { var err error - userID, err = identity.UserIdentifier(user.GetID()) + userID, err = user.GetInternalID() if err != nil { return nil, err } @@ -493,7 +493,7 @@ func (s *Service) SearchUsersPermissions(ctx context.Context, usr identity.Reque // Limit roles to available in OSS options.RolePrefixes = OSSRolesPrefixes - if options.TypedID.Type() != "" { + if options.TypedID != "" { userID, err := options.ComputeUserID() if err != nil { s.log.Error("Failed to resolve user ID", "error", err) @@ -608,7 +608,7 @@ func (s *Service) SearchUserPermissions(ctx context.Context, orgID int64, search timer := prometheus.NewTimer(metrics.MAccessPermissionsSummary) defer timer.ObserveDuration() - if searchOptions.TypedID.Type() == "" { + if searchOptions.TypedID == "" { return nil, fmt.Errorf("expected namespaced ID to be specified") } diff --git a/pkg/services/accesscontrol/api/api.go b/pkg/services/accesscontrol/api/api.go index a56f21b4f61..88241e0d1cf 100644 --- a/pkg/services/accesscontrol/api/api.go +++ b/pkg/services/accesscontrol/api/api.go @@ -5,7 +5,6 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/middleware" "github.com/grafana/grafana/pkg/middleware/requestmeta" ac "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -72,23 +71,17 @@ func (api *AccessControlAPI) searchUsersPermissions(c *contextmodel.ReqContext) ActionPrefix: c.Query("actionPrefix"), Action: c.Query("action"), Scope: c.Query("scope"), + TypedID: c.Query("namespacedId"), } - namespacedId := c.Query("namespacedId") // Validate inputs if searchOptions.ActionPrefix != "" && searchOptions.Action != "" { return response.JSON(http.StatusBadRequest, "'action' and 'actionPrefix' are mutually exclusive") } - if namespacedId == "" && searchOptions.ActionPrefix == "" && searchOptions.Action == "" { + + if searchOptions.TypedID == "" && searchOptions.ActionPrefix == "" && searchOptions.Action == "" { return response.JSON(http.StatusBadRequest, "at least one search option must be provided") } - if namespacedId != "" { - var err error - searchOptions.TypedID, err = identity.ParseTypedID(namespacedId) - if err != nil { - return response.Error(http.StatusBadGateway, "invalid namespacedId", err) - } - } // Compute metadata permissions, err := api.Service.SearchUsersPermissions(c.Req.Context(), c.SignedInUser, searchOptions) diff --git a/pkg/services/accesscontrol/authorize_in_org_test.go b/pkg/services/accesscontrol/authorize_in_org_test.go index e85ced6c2bd..5c5819b1b26 100644 --- a/pkg/services/accesscontrol/authorize_in_org_test.go +++ b/pkg/services/accesscontrol/authorize_in_org_test.go @@ -5,12 +5,12 @@ import ( "fmt" "net/http" "net/http/httptest" + "strconv" "testing" "github.com/stretchr/testify/assert" "github.com/grafana/authlib/claims" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" "github.com/grafana/grafana/pkg/services/accesscontrol/actest" @@ -188,7 +188,8 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { req := httptest.NewRequest(http.MethodGet, "/api/endpoint", nil) expectedIdentity := &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, tc.ctxSignedInUser.UserID), + ID: strconv.FormatInt(tc.ctxSignedInUser.UserID, 10), + Type: claims.TypeUser, OrgID: tc.targetOrgId, Permissions: map[int64]map[string][]string{}, } diff --git a/pkg/services/accesscontrol/database/database.go b/pkg/services/accesscontrol/database/database.go index 43c3fa448ee..afa8814c109 100644 --- a/pkg/services/accesscontrol/database/database.go +++ b/pkg/services/accesscontrol/database/database.go @@ -164,7 +164,7 @@ func (s *AccessControlStore) SearchUsersPermissions(ctx context.Context, orgID i dbPerms := make([]UserRBACPermission, 0) userID := int64(-1) - if options.TypedID.Type() != "" { + if options.TypedID != "" { var err error userID, err = options.ComputeUserID() if err != nil { diff --git a/pkg/services/anonymous/anonimpl/client.go b/pkg/services/anonymous/anonimpl/client.go index 9af940ead3d..57b4c9873fb 100644 --- a/pkg/services/anonymous/anonimpl/client.go +++ b/pkg/services/anonymous/anonimpl/client.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/authlib/claims" "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/anonymous" "github.com/grafana/grafana/pkg/services/anonymous/anonimpl/anonstore" @@ -74,7 +73,7 @@ func (a *Anonymous) IdentityType() claims.IdentityType { return claims.TypeAnonymous } -func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) { +func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, typ claims.IdentityType, id string) (*authn.Identity, error) { o, err := a.orgService.GetByName(ctx, &org.GetOrgByNameQuery{Name: a.cfg.AnonymousOrgName}) if err != nil { return nil, err @@ -85,7 +84,7 @@ func (a *Anonymous) ResolveIdentity(ctx context.Context, orgID int64, namespaceI } // Anonymous identities should always have the same namespace id. - if namespaceID != identity.AnonymousTypedID { + if !claims.IsIdentityType(typ, claims.TypeAnonymous) || id != "0" { return nil, errInvalidID } @@ -110,7 +109,8 @@ func (a *Anonymous) Priority() uint { func (a *Anonymous) newAnonymousIdentity(o *org.Org) *authn.Identity { return &authn.Identity{ - ID: identity.AnonymousTypedID, + ID: "0", + Type: claims.TypeAnonymous, OrgID: o.ID, OrgName: o.Name, OrgRoles: map[int64]org.RoleType{o.ID: org.RoleType(a.cfg.AnonymousOrgRole)}, diff --git a/pkg/services/anonymous/anonimpl/client_test.go b/pkg/services/anonymous/anonimpl/client_test.go index 7a57a51cb74..1cff62e6a25 100644 --- a/pkg/services/anonymous/anonimpl/client_test.go +++ b/pkg/services/anonymous/anonimpl/client_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/anonymous/anontest" "github.com/grafana/grafana/pkg/services/authn" @@ -60,7 +60,7 @@ func TestAnonymous_Authenticate(t *testing.T) { } else { require.Nil(t, err) - assert.Equal(t, identity.AnonymousTypedID, user.ID) + assert.Equal(t, "anonymous:0", user.GetID()) assert.Equal(t, tt.org.ID, user.OrgID) assert.Equal(t, tt.org.Name, user.OrgName) assert.Equal(t, tt.cfg.AnonymousOrgRole, string(user.GetOrgRole())) @@ -74,7 +74,8 @@ func TestAnonymous_ResolveIdentity(t *testing.T) { desc string cfg *setting.Cfg orgID int64 - namespaceID identity.TypedID + typ claims.IdentityType + id string org *org.Org orgErr error expectedErr error @@ -88,7 +89,8 @@ func TestAnonymous_ResolveIdentity(t *testing.T) { AnonymousOrgName: "some org", }, orgID: 1, - namespaceID: identity.AnonymousTypedID, + typ: claims.TypeAnonymous, + id: "0", expectedErr: errInvalidOrg, }, { @@ -98,7 +100,8 @@ func TestAnonymous_ResolveIdentity(t *testing.T) { AnonymousOrgName: "some org", }, orgID: 1, - namespaceID: identity.MustParseTypedID("anonymous:1"), + typ: claims.TypeAnonymous, + id: "1", expectedErr: errInvalidID, }, { @@ -107,8 +110,9 @@ func TestAnonymous_ResolveIdentity(t *testing.T) { cfg: &setting.Cfg{ AnonymousOrgName: "some org", }, - orgID: 1, - namespaceID: identity.AnonymousTypedID, + orgID: 1, + typ: claims.TypeAnonymous, + id: "0", }, } @@ -121,7 +125,7 @@ func TestAnonymous_ResolveIdentity(t *testing.T) { anonDeviceService: anontest.NewFakeService(), } - identity, err := c.ResolveIdentity(context.Background(), tt.orgID, tt.namespaceID) + identity, err := c.ResolveIdentity(context.Background(), tt.orgID, tt.typ, tt.id) if tt.expectedErr != nil { assert.ErrorIs(t, err, tt.expectedErr) assert.Nil(t, identity) diff --git a/pkg/services/auth/idimpl/service.go b/pkg/services/auth/idimpl/service.go index 1c696255b34..81481f1b77d 100644 --- a/pkg/services/auth/idimpl/service.go +++ b/pkg/services/auth/idimpl/service.go @@ -91,7 +91,7 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri Claims: &jwt.Claims{ Issuer: s.cfg.AppURL, Audience: getAudience(id.GetOrgID()), - Subject: id.GetID().String(), + Subject: id.GetID(), Expiry: jwt.NewNumericDate(now.Add(tokenTTL)), IssuedAt: jwt.NewNumericDate(now), }, @@ -100,7 +100,7 @@ func (s *Service) SignIdentity(ctx context.Context, id identity.Requester) (stri }, } - if identity.IsIdentityType(id.GetID(), authnlibclaims.TypeUser) { + if id.IsIdentityType(authnlibclaims.TypeUser) { claims.Rest.Email = id.GetEmail() claims.Rest.EmailVerified = id.IsEmailVerified() claims.Rest.AuthenticatedBy = id.GetAuthenticatedBy() diff --git a/pkg/services/auth/idimpl/service_test.go b/pkg/services/auth/idimpl/service_test.go index b0407743679..4ae1ea628a7 100644 --- a/pkg/services/auth/idimpl/service_test.go +++ b/pkg/services/auth/idimpl/service_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/authlib/claims" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/remotecache" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth/idtest" @@ -71,7 +70,7 @@ func TestService_SignIdentity(t *testing.T) { featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding), &authntest.FakeService{}, nil, ) - token, _, err := s.SignIdentity(context.Background(), &authn.Identity{ID: identity.MustParseTypedID("user:1")}) + token, _, err := s.SignIdentity(context.Background(), &authn.Identity{ID: "1", Type: claims.TypeUser}) require.NoError(t, err) require.NotEmpty(t, token) }) @@ -83,10 +82,12 @@ func TestService_SignIdentity(t *testing.T) { &authntest.FakeService{}, nil, ) token, _, err := s.SignIdentity(context.Background(), &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, AuthenticatedBy: login.AzureADAuthModule, Login: "U1", - UID: identity.NewTypedIDString(claims.TypeUser, "edpu3nnt61se8e")}) + UID: "edpu3nnt61se8e", + }) require.NoError(t, err) parsed, err := jwt.ParseSigned(token) @@ -106,10 +107,12 @@ func TestService_SignIdentity(t *testing.T) { &authntest.FakeService{}, nil, ) _, gotClaims, err := s.SignIdentity(context.Background(), &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, AuthenticatedBy: login.AzureADAuthModule, Login: "U1", - UID: identity.NewTypedIDString(claims.TypeUser, "edpu3nnt61se8e")}) + UID: "edpu3nnt61se8e", + }) require.NoError(t, err) assert.Equal(t, login.AzureADAuthModule, gotClaims.Rest.AuthenticatedBy) diff --git a/pkg/services/authn/authn.go b/pkg/services/authn/authn.go index f7f2686a134..9ec4ac6de35 100644 --- a/pkg/services/authn/authn.go +++ b/pkg/services/authn/authn.go @@ -95,8 +95,8 @@ type Service interface { Logout(ctx context.Context, user identity.Requester, sessionToken *usertoken.UserToken) (*Redirect, error) // RegisterPreLogoutHook registers a hook that is called before a logout request. RegisterPreLogoutHook(hook PreLogoutHookFn, priority uint) - // ResolveIdentity resolves an identity from org and namespace id. - ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*Identity, error) + // ResolveIdentity resolves an identity from orgID and typedID. + ResolveIdentity(ctx context.Context, orgID int64, typedID string) (*Identity, error) // RegisterClient will register a new authn.Client that can be used for authentication RegisterClient(c Client) @@ -177,11 +177,11 @@ type UsageStatClient interface { } // IdentityResolverClient is an optional interface that auth clients can implement. -// Clients that implements this interface can resolve an full identity from an orgID and namespaceID. +// Clients that implements this interface can resolve an full identity from an orgID and typedID. type IdentityResolverClient interface { Client IdentityType() claims.IdentityType - ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*Identity, error) + ResolveIdentity(ctx context.Context, orgID int64, typ claims.IdentityType, id string) (*Identity, error) } type Request struct { diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index 63f587e5ca4..9d6e047f22e 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -7,12 +7,12 @@ import ( "strconv" "strings" + "github.com/grafana/authlib/claims" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" "go.opentelemetry.io/otel/trace" - "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" @@ -145,9 +145,9 @@ func (s *Service) authenticate(ctx context.Context, c authn.Client, r *authn.Req } span.SetAttributes( - attribute.String("identity.ID", identity.ID.String()), - attribute.String("identity.AuthID", identity.AuthID), - attribute.String("identity.AuthenticatedBy", identity.AuthenticatedBy), + attribute.String("identity.ID", identity.GetID()), + attribute.String("identity.AuthID", identity.GetAuthID()), + attribute.String("identity.AuthenticatedBy", identity.GetAuthenticatedBy()), ) if len(identity.ClientParams.FetchPermissionsParams.ActionsLookup) > 0 { @@ -218,12 +218,12 @@ func (s *Service) Login(ctx context.Context, client string, r *authn.Request) (i } // Login is only supported for users - if !id.ID.IsType(claims.TypeUser) { + if !id.IsIdentityType(claims.TypeUser) { s.metrics.failedLogin.WithLabelValues(client).Inc() - return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", id.ID.Type()) + return nil, authn.ErrUnsupportedIdentity.Errorf("expected identity of type user but got: %s", id.GetIdentityType()) } - userID, err := id.ID.ParseInt() + userID, err := id.GetInternalID() if err != nil { return nil, err } @@ -282,11 +282,11 @@ func (s *Service) Logout(ctx context.Context, user identity.Requester, sessionTo redirect.URL = s.cfg.SignoutRedirectUrl } - if !user.GetID().IsType(claims.TypeUser) { + if !user.IsIdentityType(claims.TypeUser) { return redirect, nil } - id, err := user.GetID().ParseInt() + id, err := user.GetInternalID() if err != nil { s.log.FromContext(ctx).Debug("Invalid user id", "id", id, "err", err) return redirect, nil @@ -329,7 +329,7 @@ Default: return redirect, nil } -func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) { +func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, typedID string) (*authn.Identity, error) { ctx, span := s.tracer.Start(ctx, "authn.ResolveIdentity") defer span.End() @@ -338,8 +338,12 @@ func (s *Service) ResolveIdentity(ctx context.Context, orgID int64, namespaceID // hack to not update last seen r.SetMeta(authn.MetaKeyIsLogin, "true") - identity, err := s.resolveIdenity(ctx, orgID, namespaceID) + identity, err := s.resolveIdenity(ctx, orgID, typedID) if err != nil { + if errors.Is(err, claims.ErrInvalidTypedID) { + return nil, authn.ErrUnsupportedIdentity.Errorf("invalid identity type") + } + return nil, err } @@ -377,14 +381,20 @@ func (s *Service) SyncIdentity(ctx context.Context, identity *authn.Identity) er return s.runPostAuthHooks(ctx, identity, r) } -func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) { +func (s *Service) resolveIdenity(ctx context.Context, orgID int64, typedID string) (*authn.Identity, error) { ctx, span := s.tracer.Start(ctx, "authn.resolveIdentity") defer span.End() - if namespaceID.IsType(claims.TypeUser) { + t, i, err := identity.ParseTypeAndID(typedID) + if err != nil { + return nil, err + } + + if claims.IsIdentityType(t, claims.TypeUser) { return &authn.Identity{ OrgID: orgID, - ID: namespaceID, + ID: i, + Type: claims.TypeUser, ClientParams: authn.ClientParams{ AllowGlobalOrg: true, FetchSyncedUser: true, @@ -392,9 +402,10 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID i }}, nil } - if namespaceID.IsType(claims.TypeServiceAccount) { + if claims.IsIdentityType(t, claims.TypeServiceAccount) { return &authn.Identity{ - ID: namespaceID, + ID: i, + Type: claims.TypeServiceAccount, OrgID: orgID, ClientParams: authn.ClientParams{ AllowGlobalOrg: true, @@ -403,11 +414,11 @@ func (s *Service) resolveIdenity(ctx context.Context, orgID int64, namespaceID i }}, nil } - resolver, ok := s.idenityResolverClients[string(namespaceID.Type())] + resolver, ok := s.idenityResolverClients[string(t)] if !ok { - return nil, authn.ErrUnsupportedIdentity.Errorf("no resolver for : %s", namespaceID.Type()) + return nil, authn.ErrUnsupportedIdentity.Errorf("no resolver for : %s", t) } - return resolver.ResolveIdentity(ctx, orgID, namespaceID) + return resolver.ResolveIdentity(ctx, orgID, t, i) } func (s *Service) errorLogFunc(ctx context.Context, err error) func(msg string, ctx ...any) { diff --git a/pkg/services/authn/authnimpl/service_test.go b/pkg/services/authn/authnimpl/service_test.go index 8eb0f49846c..216de8300d8 100644 --- a/pkg/services/authn/authnimpl/service_test.go +++ b/pkg/services/authn/authnimpl/service_test.go @@ -9,13 +9,13 @@ import ( "slices" "testing" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/codes" sdktrace "go.opentelemetry.io/otel/sdk/trace" "go.opentelemetry.io/otel/sdk/trace/tracetest" - "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -45,9 +45,9 @@ func TestService_Authenticate(t *testing.T) { { desc: "should succeed with authentication for configured client", clients: []authn.Client{ - &authntest.FakeClient{ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}}, + &authntest.FakeClient{ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: "1", Type: claims.TypeUser}}, }, - expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + expectedIdentity: &authn.Identity{ID: "1", Type: claims.TypeUser}, }, { desc: "should succeed with authentication for configured client for identity with fetch permissions params", @@ -55,7 +55,8 @@ func TestService_Authenticate(t *testing.T) { &authntest.FakeClient{ ExpectedTest: true, ExpectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("user:2"), + ID: "2", + Type: claims.TypeUser, ClientParams: authn.ClientParams{ FetchPermissionsParams: authn.FetchPermissionsParams{ ActionsLookup: []string{ @@ -71,7 +72,8 @@ func TestService_Authenticate(t *testing.T) { }, }, expectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("user:2"), + ID: "2", + Type: claims.TypeUser, ClientParams: authn.ClientParams{ FetchPermissionsParams: authn.FetchPermissionsParams{ ActionsLookup: []string{ @@ -93,19 +95,19 @@ func TestService_Authenticate(t *testing.T) { ExpectedName: "2", ExpectedPriority: 2, ExpectedTest: true, - ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"}, + ExpectedIdentity: &authn.Identity{ID: "2", Type: claims.TypeUser, AuthID: "service:some-service", AuthenticatedBy: "service_auth"}, }, }, - expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), AuthID: "service:some-service", AuthenticatedBy: "service_auth"}, + expectedIdentity: &authn.Identity{ID: "2", Type: claims.TypeUser, AuthID: "service:some-service", AuthenticatedBy: "service_auth"}, }, { desc: "should succeed with authentication for third client when error happened in first", clients: []authn.Client{ &authntest.FakeClient{ExpectedName: "1", ExpectedPriority: 2, ExpectedTest: false}, &authntest.FakeClient{ExpectedName: "2", ExpectedPriority: 1, ExpectedTest: true, ExpectedErr: errors.New("some error")}, - &authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:3")}}, + &authntest.FakeClient{ExpectedName: "3", ExpectedPriority: 3, ExpectedTest: true, ExpectedIdentity: &authn.Identity{ID: "3", Type: claims.TypeUser}}, }, - expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:3")}, + expectedIdentity: &authn.Identity{ID: "3", Type: claims.TypeUser}, }, { desc: "should return error when no client could authenticate the request", @@ -180,7 +182,7 @@ func TestService_Authenticate(t *testing.T) { for _, attr := range passedAuthnSpan.Attributes() { switch attr.Key { case "identity.ID": - assert.Equal(t, tt.expectedIdentity.ID.String(), attr.Value.AsString()) + assert.Equal(t, tt.expectedIdentity.GetID(), attr.Value.AsString()) case "identity.AuthID": assert.Equal(t, tt.expectedIdentity.AuthID, attr.Value.AsString()) case "identity.AuthenticatedBy": @@ -316,10 +318,12 @@ func TestService_Login(t *testing.T) { client: "fake", expectedClientOK: true, expectedClientIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, }, expectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, SessionToken: &auth.UserToken{UserId: 1}, }, }, @@ -332,7 +336,7 @@ func TestService_Login(t *testing.T) { desc: "should not login non user identity", client: "fake", expectedClientOK: true, - expectedClientIdentity: &authn.Identity{ID: identity.MustParseTypedID("api-key:1")}, + expectedClientIdentity: &authn.Identity{ID: "1", Type: claims.TypeAPIKey}, expectedErr: authn.ErrUnsupportedIdentity, }, } @@ -421,31 +425,31 @@ func TestService_Logout(t *testing.T) { tests := []TestCase{ { desc: "should redirect to default redirect url when identity is not a user", - identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeServiceAccount, 1)}, + identity: &authn.Identity{ID: "1", Type: claims.TypeServiceAccount}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, }, { desc: "should redirect to default redirect url when no external provider was used to authenticate", - identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1)}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, expectedTokenRevoked: true, }, { desc: "should redirect to default redirect url when client is not found", - identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1), AuthenticatedBy: "notfound"}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser, AuthenticatedBy: "notfound"}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, expectedTokenRevoked: true, }, { desc: "should redirect to default redirect url when client do not implement logout extension", - identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1), AuthenticatedBy: "azuread"}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser, AuthenticatedBy: "azuread"}, expectedRedirect: &authn.Redirect{URL: "http://localhost:3000/login"}, client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"}, expectedTokenRevoked: true, }, { desc: "should use signout redirect url if configured", - identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1), AuthenticatedBy: "azuread"}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser, AuthenticatedBy: "azuread"}, expectedRedirect: &authn.Redirect{URL: "some-url"}, client: &authntest.FakeClient{ExpectedName: "auth.client.azuread"}, signoutRedirectURL: "some-url", @@ -453,7 +457,7 @@ func TestService_Logout(t *testing.T) { }, { desc: "should redirect to client specific url", - identity: &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1), AuthenticatedBy: "azuread"}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser, AuthenticatedBy: "azuread"}, expectedRedirect: &authn.Redirect{URL: "http://idp.com/logout"}, client: &authntest.MockClient{ NameFunc: func() string { return "auth.client.azuread" }, @@ -501,26 +505,26 @@ func TestService_Logout(t *testing.T) { func TestService_ResolveIdentity(t *testing.T) { t.Run("should return error for for unknown namespace", func(t *testing.T) { svc := setupTests(t) - _, err := svc.ResolveIdentity(context.Background(), 1, identity.NewTypedID("some", 1)) + _, err := svc.ResolveIdentity(context.Background(), 1, "some:1") assert.ErrorIs(t, err, authn.ErrUnsupportedIdentity) }) t.Run("should return error for for namespace that don't have a resolver", func(t *testing.T) { svc := setupTests(t) - _, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("api-key:1")) + _, err := svc.ResolveIdentity(context.Background(), 1, "api-key:1") assert.ErrorIs(t, err, authn.ErrUnsupportedIdentity) }) t.Run("should resolve for user", func(t *testing.T) { svc := setupTests(t) - identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("user:1")) + identity, err := svc.ResolveIdentity(context.Background(), 1, "user:1") assert.NoError(t, err) assert.NotNil(t, identity) }) t.Run("should resolve for service account", func(t *testing.T) { svc := setupTests(t) - identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("service-account:1")) + identity, err := svc.ResolveIdentity(context.Background(), 1, "service-account:1") assert.NoError(t, err) assert.NotNil(t, identity) }) @@ -529,13 +533,13 @@ func TestService_ResolveIdentity(t *testing.T) { svc := setupTests(t, func(svc *Service) { svc.RegisterClient(&authntest.MockClient{ IdentityTypeFunc: func() claims.IdentityType { return claims.TypeAPIKey }, - ResolveIdentityFunc: func(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) { + ResolveIdentityFunc: func(_ context.Context, _ int64, _ claims.IdentityType, _ string) (*authn.Identity, error) { return &authn.Identity{}, nil }, }) }) - identity, err := svc.ResolveIdentity(context.Background(), 1, identity.MustParseTypedID("api-key:1")) + identity, err := svc.ResolveIdentity(context.Background(), 1, "api-key:1") assert.NoError(t, err) assert.NotNil(t, identity) }) diff --git a/pkg/services/authn/authnimpl/sync/oauth_token_sync.go b/pkg/services/authn/authnimpl/sync/oauth_token_sync.go index cecc3cc5218..d68c3925317 100644 --- a/pkg/services/authn/authnimpl/sync/oauth_token_sync.go +++ b/pkg/services/authn/authnimpl/sync/oauth_token_sync.go @@ -6,9 +6,9 @@ import ( "strings" "time" + "github.com/grafana/authlib/claims" "golang.org/x/sync/singleflight" - "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/login/social" @@ -42,7 +42,7 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, id *authn.Ident defer span.End() // only perform oauth token check if identity is a user - if !id.ID.IsType(claims.TypeUser) { + if !id.IsIdentityType(claims.TypeUser) { return nil } @@ -56,9 +56,9 @@ func (s *OAuthTokenSync) SyncOauthTokenHook(ctx context.Context, id *authn.Ident return nil } - ctxLogger := s.log.FromContext(ctx).New("userID", id.ID.ID()) + ctxLogger := s.log.FromContext(ctx).New("userID", id.GetID()) - _, err, _ := s.singleflightGroup.Do(id.ID.String(), func() (interface{}, error) { + _, err, _ := s.singleflightGroup.Do(id.GetID(), func() (interface{}, error) { ctxLogger.Debug("Singleflight request for OAuth token sync") // FIXME: Consider using context.WithoutCancel instead of context.Background after Go 1.21 update diff --git a/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go b/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go index 326a8d1eea7..8c35cf652c1 100644 --- a/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/oauth_token_sync_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "golang.org/x/sync/singleflight" @@ -42,17 +43,17 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) { tests := []testCase{ { desc: "should skip sync when identity is not a user", - identity: &authn.Identity{ID: identity.MustParseTypedID("service-account:1")}, + identity: &authn.Identity{ID: "1", Type: claims.TypeServiceAccount}, expectTryRefreshTokenCalled: false, }, { desc: "should skip sync when identity is a user but is not authenticated with session token", - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser}, expectTryRefreshTokenCalled: false, }, { desc: "should invalidate access token and session token if token refresh fails", - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser, SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, expectHasEntryCalled: true, expectedTryRefreshErr: errors.New("some err"), expectTryRefreshTokenCalled: true, @@ -63,7 +64,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) { }, { desc: "should refresh the token successfully", - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser, SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, expectHasEntryCalled: false, expectTryRefreshTokenCalled: true, expectInvalidateOauthTokensCalled: false, @@ -71,7 +72,7 @@ func TestOAuthTokenSync_SyncOAuthTokenHook(t *testing.T) { }, { desc: "should not invalidate the token if the token has already been refreshed by another request (singleflight)", - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1"), SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser, SessionToken: &auth.UserToken{}, AuthenticatedBy: login.AzureADAuthModule}, expectHasEntryCalled: true, expectTryRefreshTokenCalled: true, expectInvalidateOauthTokensCalled: false, diff --git a/pkg/services/authn/authnimpl/sync/org_sync.go b/pkg/services/authn/authnimpl/sync/org_sync.go index 0ef1d5150d2..87d79215ce5 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync.go +++ b/pkg/services/authn/authnimpl/sync/org_sync.go @@ -39,14 +39,14 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a ctxLogger := s.log.FromContext(ctx).New("id", id.ID, "login", id.Login) - if !id.ID.IsType(claims.TypeUser) { - ctxLogger.Warn("Failed to sync org role, invalid namespace for identity", "type", id.ID.Type()) + if !id.IsIdentityType(claims.TypeUser) { + ctxLogger.Warn("Failed to sync org role, invalid namespace for identity", "type", id.GetIdentityType()) return nil } - userID, err := id.ID.ParseInt() + userID, err := id.GetInternalID() if err != nil { - ctxLogger.Warn("Failed to sync org role, invalid ID for identity", "type", id.ID.Type(), "err", err) + ctxLogger.Warn("Failed to sync org role, invalid ID for identity", "type", id.GetIdentityType(), "err", err) return nil } @@ -145,14 +145,14 @@ func (s *OrgSync) SetDefaultOrgHook(ctx context.Context, currentIdentity *authn. ctxLogger := s.log.FromContext(ctx) - if !currentIdentity.ID.IsType(claims.TypeUser) { - ctxLogger.Debug("Skipping default org sync, not a user", "type", currentIdentity.ID.Type()) + if !currentIdentity.IsIdentityType(claims.TypeUser) { + ctxLogger.Debug("Skipping default org sync, not a user", "type", currentIdentity.GetIdentityType()) return } - userID, err := currentIdentity.ID.ParseInt() + userID, err := currentIdentity.GetInternalID() if err != nil { - ctxLogger.Debug("Skipping default org sync, invalid ID for identity", "id", currentIdentity.ID, "type", currentIdentity.ID.Type(), "err", err) + ctxLogger.Debug("Skipping default org sync, invalid ID for identity", "id", currentIdentity.ID, "type", currentIdentity.GetIdentityType(), "err", err) return } diff --git a/pkg/services/authn/authnimpl/sync/org_sync_test.go b/pkg/services/authn/authnimpl/sync/org_sync_test.go index b6d14096783..b62bcbcbf00 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/org_sync_test.go @@ -5,6 +5,7 @@ import ( "fmt" "testing" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -76,7 +77,8 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { args: args{ ctx: context.Background(), id: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, Login: "test", Name: "test", Email: "test", @@ -92,7 +94,8 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { }, }, wantID: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, Login: "test", Name: "test", Email: "test", @@ -139,7 +142,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { { name: "should set default org", defaultOrgSetting: 2, - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser}, setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { userService.On("Update", mock.Anything, mock.MatchedBy(func(cmd *user.UpdateUserCommand) bool { return cmd.UserID == 1 && *cmd.OrgID == 2 @@ -149,7 +152,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { { name: "should skip setting the default org when default org is not set", defaultOrgSetting: -1, - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser}, }, { name: "should skip setting the default org when identity is nil", @@ -159,28 +162,28 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { { name: "should skip setting the default org when input err is not nil", defaultOrgSetting: 2, - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser}, inputErr: fmt.Errorf("error"), }, { name: "should skip setting the default org when identity is not a user", defaultOrgSetting: 2, - identity: &authn.Identity{ID: identity.MustParseTypedID("service-account:1")}, + identity: &authn.Identity{ID: "1", Type: claims.TypeServiceAccount}, }, { name: "should skip setting the default org when user id is not valid", defaultOrgSetting: 2, - identity: &authn.Identity{ID: identity.MustParseTypedID("user:invalid")}, + identity: &authn.Identity{ID: "invalid", Type: claims.TypeUser}, }, { name: "should skip setting the default org when user is not allowed to use the configured default org", defaultOrgSetting: 3, - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser}, }, { name: "should skip setting the default org when validateUsingOrg returns error", defaultOrgSetting: 2, - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser}, setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { orgService.ExpectedError = fmt.Errorf("error") }, @@ -188,7 +191,7 @@ func TestOrgSync_SetDefaultOrgHook(t *testing.T) { { name: "should skip the hook when the user org update was unsuccessful", defaultOrgSetting: 2, - identity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + identity: &authn.Identity{ID: "1", Type: claims.TypeUser}, setupMock: func(userService *usertest.MockService, orgService *orgtest.FakeOrgService) { userService.On("Update", mock.Anything, mock.Anything).Return(fmt.Errorf("error")) }, diff --git a/pkg/services/authn/authnimpl/sync/rbac_sync.go b/pkg/services/authn/authnimpl/sync/rbac_sync.go index e9ed94ff0d6..f5b11c687ca 100644 --- a/pkg/services/authn/authnimpl/sync/rbac_sync.go +++ b/pkg/services/authn/authnimpl/sync/rbac_sync.go @@ -148,12 +148,12 @@ func (s *RBACSync) SyncCloudRoles(ctx context.Context, ident *authn.Identity, r return nil } - if !ident.ID.IsType(claims.TypeUser) { + if !ident.IsIdentityType(claims.TypeUser) { s.log.FromContext(ctx).Debug("Skip syncing cloud role", "id", ident.ID) return nil } - userID, err := ident.ID.ParseInt() + userID, err := ident.GetInternalID() if err != nil { return err } diff --git a/pkg/services/authn/authnimpl/sync/rbac_sync_test.go b/pkg/services/authn/authnimpl/sync/rbac_sync_test.go index f38b83679ed..08e3434d15d 100644 --- a/pkg/services/authn/authnimpl/sync/rbac_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/rbac_sync_test.go @@ -4,10 +4,10 @@ import ( "context" "testing" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -27,14 +27,14 @@ func TestRBACSync_SyncPermission(t *testing.T) { testCases := []testCase{ { name: "enriches the identity successfully when SyncPermissions is true", - identity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}}, + identity: &authn.Identity{ID: "2", Type: claims.TypeUser, OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}}, expectedPermissions: []accesscontrol.Permission{ {Action: accesscontrol.ActionUsersRead}, }, }, { name: "does not load the permissions when SyncPermissions is false", - identity: &authn.Identity{ID: identity.MustParseTypedID("user:2"), OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}}, + identity: &authn.Identity{ID: "2", Type: claims.TypeUser, OrgID: 1, ClientParams: authn.ClientParams{SyncPermissions: true}}, expectedPermissions: []accesscontrol.Permission{ {Action: accesscontrol.ActionUsersRead}, }, @@ -68,7 +68,8 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should call sync when authenticated with grafana com and has viewer role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, 1), + ID: "1", + Type: claims.TypeUser, OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, }, @@ -79,7 +80,8 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should call sync when authenticated with grafana com and has editor role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, 1), + ID: "1", + Type: claims.TypeUser, OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleEditor}, }, @@ -90,7 +92,8 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should call sync when authenticated with grafana com and has admin role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, 1), + ID: "1", + Type: claims.TypeUser, OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, }, @@ -101,7 +104,8 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should not call sync when authenticated with grafana com and has invalid role", module: login.GrafanaComAuthModule, identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, 1), + ID: "1", + Type: claims.TypeUser, OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleType("something else")}, }, @@ -112,7 +116,8 @@ func TestRBACSync_SyncCloudRoles(t *testing.T) { desc: "should not call sync when not authenticated with grafana com", module: login.LDAPAuthModule, identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, 1), + ID: "1", + Type: claims.TypeUser, OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, }, @@ -158,7 +163,8 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) { { desc: "should map Cloud Viewer to Grafana Cloud Viewer and Support ticket reader", identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, 1), + ID: "1", + Type: claims.TypeUser, OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, }, @@ -177,7 +183,8 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) { { desc: "should map Cloud Editor to Grafana Cloud Editor and Support ticket admin", identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, 1), + ID: "1", + Type: claims.TypeUser, OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleEditor}, }, @@ -195,7 +202,8 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) { { desc: "should map Cloud Admin to Grafana Cloud Admin and Support ticket admin", identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, 1), + ID: "1", + Type: claims.TypeUser, OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, }, @@ -213,7 +221,8 @@ func TestRBACSync_cloudRolesToAddAndRemove(t *testing.T) { { desc: "should return an error for not supported role", identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, 1), + ID: "1", + Type: claims.TypeUser, OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleNone}, }, diff --git a/pkg/services/authn/authnimpl/sync/user_sync.go b/pkg/services/authn/authnimpl/sync/user_sync.go index 4c00d809b38..fa682806ea3 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync.go +++ b/pkg/services/authn/authnimpl/sync/user_sync.go @@ -4,10 +4,11 @@ import ( "context" "errors" "fmt" + "strconv" "github.com/grafana/authlib/claims" + "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/infra/tracing" "github.com/grafana/grafana/pkg/services/authn" @@ -119,11 +120,11 @@ func (s *UserSync) FetchSyncedUserHook(ctx context.Context, id *authn.Identity, return nil } - if !id.ID.IsType(claims.TypeUser, claims.TypeServiceAccount) { + if !id.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { return nil } - userID, err := id.ID.ParseInt() + userID, err := id.GetInternalID() if err != nil { s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err) return nil @@ -160,11 +161,11 @@ func (s *UserSync) SyncLastSeenHook(ctx context.Context, id *authn.Identity, r * return nil } - if !id.ID.IsType(claims.TypeUser, claims.TypeServiceAccount) { + if !id.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { return nil } - userID, err := id.ID.ParseInt() + userID, err := id.GetInternalID() if err != nil { s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err) return nil @@ -196,11 +197,11 @@ func (s *UserSync) EnableUserHook(ctx context.Context, id *authn.Identity, _ *au return nil } - if !id.ID.IsType(claims.TypeUser) { + if !id.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { return nil } - userID, err := id.ID.ParseInt() + userID, err := id.GetInternalID() if err != nil { s.log.FromContext(ctx).Warn("got invalid identity ID", "id", id.ID, "err", err) return nil @@ -419,8 +420,9 @@ func (s *UserSync) lookupByOneOf(ctx context.Context, params login.UserLookupPar // syncUserToIdentity syncs a user to an identity. // This is used to update the identity with the latest user information. func syncUserToIdentity(usr *user.User, id *authn.Identity) { - id.ID = identity.NewTypedID(claims.TypeUser, usr.ID) - id.UID = identity.NewTypedIDString(claims.TypeUser, usr.UID) + id.ID = strconv.FormatInt(usr.ID, 10) + id.UID = usr.UID + id.Type = claims.TypeUser id.Login = usr.Login id.Email = usr.Email id.Name = usr.Name @@ -430,14 +432,7 @@ func syncUserToIdentity(usr *user.User, id *authn.Identity) { // syncSignedInUserToIdentity syncs a user to an identity. func syncSignedInUserToIdentity(usr *user.SignedInUser, id *authn.Identity) { - var ns claims.IdentityType - if id.ID.IsType(claims.TypeServiceAccount) { - ns = claims.TypeServiceAccount - } else { - ns = claims.TypeUser - } - id.UID = identity.NewTypedIDString(ns, usr.UserUID) - + id.UID = usr.UserUID id.Name = usr.Name id.Login = usr.Login id.Email = usr.Email diff --git a/pkg/services/authn/authnimpl/sync/user_sync_test.go b/pkg/services/authn/authnimpl/sync/user_sync_test.go index 82d76e729aa..a03729420ab 100644 --- a/pkg/services/authn/authnimpl/sync/user_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/user_sync_test.go @@ -4,11 +4,10 @@ import ( "context" "testing" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/grafana/authlib/claims" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" @@ -165,8 +164,9 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), - UID: identity.MustParseTypedID("user:1"), + ID: "1", + UID: "1", + Type: claims.TypeUser, Login: "test", Name: "test", Email: "test", @@ -204,8 +204,9 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), - UID: identity.MustParseTypedID("user:1"), + ID: "1", + UID: "1", + Type: claims.TypeUser, Login: "test", Name: "test", Email: "test", @@ -245,8 +246,9 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), - UID: identity.MustParseTypedID("user:1"), + ID: "1", + UID: "1", + Type: claims.TypeUser, AuthID: "2032", AuthenticatedBy: "oauth", Login: "test", @@ -317,8 +319,9 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: identity.MustParseTypedID("user:2"), - UID: identity.MustParseTypedID("user:2"), + ID: "2", + UID: "2", + Type: claims.TypeUser, Login: "test_create", Name: "test_create", Email: "test_create", @@ -363,8 +366,9 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: identity.MustParseTypedID("user:3"), - UID: identity.MustParseTypedID("user:3"), + ID: "3", + UID: "3", + Type: claims.TypeUser, Login: "test_mod", Name: "test_mod", Email: "test_mod", @@ -408,8 +412,9 @@ func TestUserSync_SyncUserHook(t *testing.T) { }, wantErr: false, wantID: &authn.Identity{ - ID: identity.MustParseTypedID("user:3"), - UID: identity.MustParseTypedID("user:3"), + ID: "3", + UID: "3", + Type: claims.TypeUser, Name: "test", Login: "test", Email: "test_mod@test.com", @@ -459,7 +464,7 @@ func TestUserSync_FetchSyncedUserHook(t *testing.T) { { desc: "should skip hook when identity is not a user", req: &authn.Request{}, - identity: &authn.Identity{ID: identity.MustParseTypedID("api-key:1"), ClientParams: authn.ClientParams{FetchSyncedUser: true}}, + identity: &authn.Identity{ID: "1", Type: claims.TypeAPIKey, ClientParams: authn.ClientParams{FetchSyncedUser: true}}, }, } @@ -485,7 +490,8 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) { { desc: "should skip if correct flag is not set", identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, 1), + ID: "1", + Type: claims.TypeUser, IsDisabled: true, ClientParams: authn.ClientParams{EnableUser: false}, }, @@ -494,7 +500,8 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) { { desc: "should skip if identity is not a user", identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeAPIKey, 1), + ID: "1", + Type: claims.TypeAPIKey, IsDisabled: true, ClientParams: authn.ClientParams{EnableUser: true}, }, @@ -503,7 +510,8 @@ func TestUserSync_EnableDisabledUserHook(t *testing.T) { { desc: "should enabled disabled user", identity: &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, 1), + ID: "1", + Type: claims.TypeUser, IsDisabled: true, ClientParams: authn.ClientParams{EnableUser: true}, }, diff --git a/pkg/services/authn/authntest/fake.go b/pkg/services/authn/authntest/fake.go index 8589ae9a812..91fc6473032 100644 --- a/pkg/services/authn/authntest/fake.go +++ b/pkg/services/authn/authntest/fake.go @@ -78,7 +78,7 @@ func (f *FakeService) Logout(_ context.Context, _ identity.Requester, _ *usertok panic("unimplemented") } -func (f *FakeService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) { +func (f *FakeService) ResolveIdentity(ctx context.Context, orgID int64, typedID string) (*authn.Identity, error) { if f.ExpectedIdentities != nil { if f.CurrentIndex >= len(f.ExpectedIdentities) { panic("ExpectedIdentities is empty") diff --git a/pkg/services/authn/authntest/mock.go b/pkg/services/authn/authntest/mock.go index 81dad579b61..f9422291f1d 100644 --- a/pkg/services/authn/authntest/mock.go +++ b/pkg/services/authn/authntest/mock.go @@ -55,7 +55,7 @@ func (*MockService) Logout(_ context.Context, _ identity.Requester, _ *usertoken panic("unimplemented") } -func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) { +func (m *MockService) ResolveIdentity(ctx context.Context, orgID int64, typedID string) (*authn.Identity, error) { panic("unimplemented") } @@ -79,7 +79,7 @@ type MockClient struct { HookFunc func(ctx context.Context, identity *authn.Identity, r *authn.Request) error LogoutFunc func(ctx context.Context, user identity.Requester) (*authn.Redirect, bool) IdentityTypeFunc func() claims.IdentityType - ResolveIdentityFunc func(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) + ResolveIdentityFunc func(ctx context.Context, orgID int64, typ claims.IdentityType, id string) (*authn.Identity, error) } func (m MockClient) Name() string { @@ -136,9 +136,9 @@ func (m *MockClient) IdentityType() claims.IdentityType { } // ResolveIdentity implements authn.IdentityResolverClient. -func (m *MockClient) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) { +func (m *MockClient) ResolveIdentity(ctx context.Context, orgID int64, typ claims.IdentityType, id string) (*authn.Identity, error) { if m.ResolveIdentityFunc != nil { - return m.ResolveIdentityFunc(ctx, orgID, namespaceID) + return m.ResolveIdentityFunc(ctx, orgID, typ, id) } return nil, nil } diff --git a/pkg/services/authn/clients/api_key.go b/pkg/services/authn/clients/api_key.go index fdab410c979..3750c87d71a 100644 --- a/pkg/services/authn/clients/api_key.go +++ b/pkg/services/authn/clients/api_key.go @@ -3,6 +3,7 @@ package clients import ( "context" "errors" + "strconv" "strings" "time" @@ -140,12 +141,12 @@ func (s *APIKey) IdentityType() claims.IdentityType { return claims.TypeAPIKey } -func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, namespaceID identity.TypedID) (*authn.Identity, error) { - if !namespaceID.IsType(claims.TypeAPIKey) { - return nil, identity.ErrInvalidTypedID.Errorf("got unspected namespace: %s", namespaceID.Type()) +func (s *APIKey) ResolveIdentity(ctx context.Context, orgID int64, typ claims.IdentityType, id string) (*authn.Identity, error) { + if !claims.IsIdentityType(typ, claims.TypeAPIKey) { + return nil, identity.ErrInvalidTypedID.Errorf("got unexpected type: %s", typ) } - apiKeyID, err := namespaceID.ParseInt() + apiKeyID, err := strconv.ParseInt(id, 10, 64) if err != nil { return nil, err } @@ -190,17 +191,17 @@ func (s *APIKey) Hook(ctx context.Context, identity *authn.Identity, r *authn.Re } func (s *APIKey) getAPIKeyID(ctx context.Context, id *authn.Identity, r *authn.Request) (apiKeyID int64, exists bool) { - internalId, err := id.ID.ParseInt() + internalId, err := id.GetInternalID() if err != nil { s.log.Warn("Failed to parse ID from identifier", "err", err) return -1, false } - if id.ID.IsType(claims.TypeAPIKey) { + if id.IsIdentityType(claims.TypeAPIKey) { return internalId, true } - if id.ID.IsType(claims.TypeServiceAccount) { + if id.IsIdentityType(claims.TypeServiceAccount) { // When the identity is service account, the ID in from the namespace is the service account ID. // We need to fetch the API key in this scenario, as we could use it to uniquely identify a service account token. apiKey, err := s.getAPIKey(ctx, getTokenFromRequest(r)) @@ -257,7 +258,8 @@ func validateApiKey(orgID int64, key *apikey.APIKey) error { func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity { return &authn.Identity{ - ID: identity.NewTypedID(claims.TypeAPIKey, key.ID), + ID: strconv.FormatInt(key.ID, 10), + Type: claims.TypeAPIKey, OrgID: key.OrgID, OrgRoles: map[int64]org.RoleType{key.OrgID: key.Role}, ClientParams: authn.ClientParams{SyncPermissions: true}, @@ -267,7 +269,8 @@ func newAPIKeyIdentity(key *apikey.APIKey) *authn.Identity { func newServiceAccountIdentity(key *apikey.APIKey) *authn.Identity { return &authn.Identity{ - ID: identity.NewTypedID(claims.TypeServiceAccount, *key.ServiceAccountId), + ID: strconv.FormatInt(*key.ServiceAccountId, 10), + Type: claims.TypeServiceAccount, OrgID: key.OrgID, AuthenticatedBy: login.APIKeyAuthModule, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, diff --git a/pkg/services/authn/clients/api_key_test.go b/pkg/services/authn/clients/api_key_test.go index 97e295f72b9..fc4fe215215 100644 --- a/pkg/services/authn/clients/api_key_test.go +++ b/pkg/services/authn/clients/api_key_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/components/apikeygen" "github.com/grafana/grafana/pkg/components/satokengen" @@ -48,7 +49,8 @@ func TestAPIKey_Authenticate(t *testing.T) { Role: org.RoleAdmin, }, expectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("api-key:1"), + ID: "1", + Type: claims.TypeAPIKey, OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleAdmin}, ClientParams: authn.ClientParams{ @@ -71,7 +73,8 @@ func TestAPIKey_Authenticate(t *testing.T) { ServiceAccountId: intPtr(1), }, expectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("service-account:1"), + ID: "1", + Type: claims.TypeServiceAccount, OrgID: 1, ClientParams: authn.ClientParams{ FetchSyncedUser: true, @@ -206,7 +209,8 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) { ServiceAccountId: intPtr(1), }, expectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("service-account:1"), + ID: "1", + Type: claims.TypeServiceAccount, OrgID: 1, Name: "test", AuthenticatedBy: login.APIKeyAuthModule, @@ -222,7 +226,8 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) { Key: hash, }, expectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("api-key:2"), + ID: "2", + Type: claims.TypeAPIKey, OrgID: 1, Name: "test", AuthenticatedBy: login.APIKeyAuthModule, @@ -238,7 +243,8 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) { Key: hash, }, expectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("user:2"), + ID: "2", + Type: claims.TypeUser, OrgID: 1, Name: "test", AuthenticatedBy: login.APIKeyAuthModule, @@ -254,7 +260,8 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) { Key: hash, }, expectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("service-account:2"), + ID: "2", + Type: claims.TypeServiceAccount, OrgID: 1, Name: "test", AuthenticatedBy: login.APIKeyAuthModule, @@ -286,8 +293,9 @@ func TestAPIKey_GetAPIKeyIDFromIdentity(t *testing.T) { func TestAPIKey_ResolveIdentity(t *testing.T) { type testCase struct { - desc string - namespaceID identity.TypedID + desc string + typ claims.IdentityType + id string exptedApiKey *apikey.APIKey @@ -297,13 +305,15 @@ func TestAPIKey_ResolveIdentity(t *testing.T) { tests := []testCase{ { - desc: "should return error for invalid namespace", - namespaceID: identity.MustParseTypedID("user:1"), + desc: "should return error for invalid type", + id: "1", + typ: claims.TypeUser, expectedErr: identity.ErrInvalidTypedID, }, { - desc: "should return error when api key has expired", - namespaceID: identity.MustParseTypedID("api-key:1"), + desc: "should return error when api key has expired", + id: "1", + typ: claims.TypeAPIKey, exptedApiKey: &apikey.APIKey{ ID: 1, OrgID: 1, @@ -312,8 +322,9 @@ func TestAPIKey_ResolveIdentity(t *testing.T) { expectedErr: errAPIKeyExpired, }, { - desc: "should return error when api key is revoked", - namespaceID: identity.MustParseTypedID("api-key:1"), + desc: "should return error when api key is revoked", + id: "1", + typ: claims.TypeAPIKey, exptedApiKey: &apikey.APIKey{ ID: 1, OrgID: 1, @@ -322,8 +333,10 @@ func TestAPIKey_ResolveIdentity(t *testing.T) { expectedErr: errAPIKeyRevoked, }, { - desc: "should return error when api key is connected to service account", - namespaceID: identity.MustParseTypedID("api-key:1"), + desc: "should return error when api key is connected to service account", + id: "1", + typ: claims.TypeAPIKey, + exptedApiKey: &apikey.APIKey{ ID: 1, OrgID: 1, @@ -332,8 +345,9 @@ func TestAPIKey_ResolveIdentity(t *testing.T) { expectedErr: identity.ErrInvalidTypedID, }, { - desc: "should return error when api key is belongs to different org", - namespaceID: identity.MustParseTypedID("api-key:1"), + desc: "should return error when api key is belongs to different org", + id: "1", + typ: claims.TypeAPIKey, exptedApiKey: &apikey.APIKey{ ID: 1, OrgID: 2, @@ -342,8 +356,9 @@ func TestAPIKey_ResolveIdentity(t *testing.T) { expectedErr: errAPIKeyOrgMismatch, }, { - desc: "should return valid idenitty", - namespaceID: identity.MustParseTypedID("api-key:1"), + desc: "should return valid idenitty", + id: "1", + typ: claims.TypeAPIKey, exptedApiKey: &apikey.APIKey{ ID: 1, OrgID: 1, @@ -352,7 +367,8 @@ func TestAPIKey_ResolveIdentity(t *testing.T) { expectedIdenity: &authn.Identity{ OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleEditor}, - ID: identity.MustParseTypedID("api-key:1"), + ID: "1", + Type: claims.TypeAPIKey, AuthenticatedBy: login.APIKeyAuthModule, ClientParams: authn.ClientParams{SyncPermissions: true}, }, @@ -365,7 +381,7 @@ func TestAPIKey_ResolveIdentity(t *testing.T) { ExpectedAPIKey: tt.exptedApiKey, }) - identity, err := c.ResolveIdentity(context.Background(), 1, tt.namespaceID) + identity, err := c.ResolveIdentity(context.Background(), 1, tt.typ, tt.id) if tt.expectedErr != nil { assert.Nil(t, identity) assert.ErrorIs(t, err, tt.expectedErr) diff --git a/pkg/services/authn/clients/basic_test.go b/pkg/services/authn/clients/basic_test.go index 7521aa7e966..6b73512dfde 100644 --- a/pkg/services/authn/clients/basic_test.go +++ b/pkg/services/authn/clients/basic_test.go @@ -5,9 +5,9 @@ import ( "net/http" "testing" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn/authntest" ) @@ -25,8 +25,8 @@ func TestBasic_Authenticate(t *testing.T) { { desc: "should success when password client return identity", req: &authn.Request{HTTPRequest: &http.Request{Header: map[string][]string{authorizationHeaderName: {encodeBasicAuth("user", "password")}}}}, - client: authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}}, - expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + client: authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: "1", Type: claims.TypeUser}}, + expectedIdentity: &authn.Identity{ID: "1", Type: claims.TypeUser}, }, { desc: "should fail when basic auth header could not be decoded", diff --git a/pkg/services/authn/clients/ext_jwt.go b/pkg/services/authn/clients/ext_jwt.go index f44d1705d6b..f1fd557fc91 100644 --- a/pkg/services/authn/clients/ext_jwt.go +++ b/pkg/services/authn/clients/ext_jwt.go @@ -9,6 +9,7 @@ import ( "github.com/go-jose/go-jose/v3/jwt" authlib "github.com/grafana/authlib/authn" authlibclaims "github.com/grafana/authlib/claims" + "github.com/grafana/grafana/pkg/apimachinery/errutil" "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" @@ -109,22 +110,22 @@ func (s *ExtendedJWT) authenticateAsUser( return nil, errExtJWTMisMatchedNamespaceClaims.Errorf("unexpected access token namespace: %s", accessTokenClaims.Rest.Namespace) } - accessID, err := identity.ParseTypedID(accessTokenClaims.Subject) + accessType, _, err := identity.ParseTypeAndID(accessTokenClaims.Subject) if err != nil { - return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessID.String()) + return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", accessTokenClaims.Subject) } - if !accessID.IsType(authlibclaims.TypeAccessPolicy) { - return nil, errExtJWTInvalid.Errorf("unexpected identity: %s", accessID.String()) + if !authlibclaims.IsIdentityType(accessType, authlibclaims.TypeAccessPolicy) { + return nil, errExtJWTInvalid.Errorf("unexpected identity: %s", accessTokenClaims.Subject) } - userID, err := identity.ParseTypedID(idTokenClaims.Subject) + t, id, err := identity.ParseTypeAndID(idTokenClaims.Subject) if err != nil { return nil, errExtJWTInvalid.Errorf("failed to parse id token subject: %w", err) } - if !userID.IsType(authlibclaims.TypeUser) { - return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", userID.String()) + if !authlibclaims.IsIdentityType(t, authlibclaims.TypeUser) { + return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", idTokenClaims.Subject) } // For use in service layer, allow higher privilege @@ -135,10 +136,11 @@ func (s *ExtendedJWT) authenticateAsUser( } return &authn.Identity{ - ID: userID, + ID: id, + Type: t, OrgID: s.getDefaultOrgID(), AuthenticatedBy: login.ExtendedJWTModule, - AuthID: accessID.String(), + AuthID: accessTokenClaims.Subject, AllowedKubernetesNamespace: allowedKubernetesNamespace, ClientParams: authn.ClientParams{ SyncPermissions: true, @@ -155,18 +157,19 @@ func (s *ExtendedJWT) authenticateAsService(claims *authlib.Claims[authlib.Acces return nil, errExtJWTDisallowedNamespaceClaim.Errorf("unexpected access token namespace: %s", claims.Rest.Namespace) } - id, err := identity.ParseTypedID(claims.Subject) + t, id, err := identity.ParseTypeAndID(claims.Subject) if err != nil { return nil, fmt.Errorf("failed to parse access token subject: %w", err) } - if !id.IsType(authlibclaims.TypeAccessPolicy) { - return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", id.String()) + if !authlibclaims.IsIdentityType(t, authlibclaims.TypeAccessPolicy) { + return nil, errExtJWTInvalidSubject.Errorf("unexpected identity: %s", claims.Subject) } return &authn.Identity{ ID: id, UID: id, + Type: t, OrgID: s.getDefaultOrgID(), AuthenticatedBy: login.ExtendedJWTModule, AuthID: claims.Subject, diff --git a/pkg/services/authn/clients/ext_jwt_test.go b/pkg/services/authn/clients/ext_jwt_test.go index e87feec4c61..bc249ac6e46 100644 --- a/pkg/services/authn/clients/ext_jwt_test.go +++ b/pkg/services/authn/clients/ext_jwt_test.go @@ -12,11 +12,12 @@ import ( "github.com/go-jose/go-jose/v3" "github.com/go-jose/go-jose/v3/jwt" authnlib "github.com/grafana/authlib/authn" - "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/services/authn" - "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/authn" + "github.com/grafana/grafana/pkg/setting" ) type ( @@ -205,8 +206,9 @@ func TestExtendedJWT_Authenticate(t *testing.T) { accessToken: &validAccessTokenClaims, orgID: 1, want: &authn.Identity{ - ID: identity.MustParseTypedID("access-policy:this-uid"), - UID: identity.MustParseTypedID("access-policy:this-uid"), + ID: "this-uid", + UID: "this-uid", + Type: claims.TypeAccessPolicy, OrgID: 1, AllowedKubernetesNamespace: "default", AuthenticatedBy: "extendedjwt", @@ -221,8 +223,9 @@ func TestExtendedJWT_Authenticate(t *testing.T) { accessToken: &validAcessTokenClaimsWildcard, orgID: 1, want: &authn.Identity{ - ID: identity.MustParseTypedID("access-policy:this-uid"), - UID: identity.MustParseTypedID("access-policy:this-uid"), + ID: "this-uid", + UID: "this-uid", + Type: claims.TypeAccessPolicy, OrgID: 1, AllowedKubernetesNamespace: "*", AuthenticatedBy: "extendedjwt", @@ -238,7 +241,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) { idToken: &validIDTokenClaims, orgID: 1, want: &authn.Identity{ - ID: identity.MustParseTypedID("user:2"), + ID: "2", + Type: claims.TypeUser, OrgID: 1, AllowedKubernetesNamespace: "default", AuthenticatedBy: "extendedjwt", @@ -258,7 +262,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) { idToken: &validIDTokenClaims, orgID: 1, want: &authn.Identity{ - ID: identity.MustParseTypedID("user:2"), + ID: "2", + Type: claims.TypeUser, OrgID: 1, AllowedKubernetesNamespace: "*", AuthenticatedBy: "extendedjwt", @@ -283,7 +288,8 @@ func TestExtendedJWT_Authenticate(t *testing.T) { }, }, want: &authn.Identity{ - ID: identity.MustParseTypedID("user:2"), + ID: "2", + Type: claims.TypeUser, OrgID: 1, AllowedKubernetesNamespace: "stack-1234", AuthenticatedBy: "extendedjwt", diff --git a/pkg/services/authn/clients/grafana.go b/pkg/services/authn/clients/grafana.go index 04de6c88151..f41d40ee2b0 100644 --- a/pkg/services/authn/clients/grafana.go +++ b/pkg/services/authn/clients/grafana.go @@ -5,9 +5,9 @@ import ( "crypto/subtle" "errors" "net/mail" + "strconv" "github.com/grafana/authlib/claims" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -107,7 +107,8 @@ func (c *Grafana) AuthenticatePassword(ctx context.Context, r *authn.Request, us } return &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, usr.ID), + ID: strconv.FormatInt(usr.ID, 10), + Type: claims.TypeUser, OrgID: r.OrgID, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, AuthenticatedBy: login.PasswordAuthModule, diff --git a/pkg/services/authn/clients/grafana_test.go b/pkg/services/authn/clients/grafana_test.go index 4a1f5f2955c..aef2e165f9a 100644 --- a/pkg/services/authn/clients/grafana_test.go +++ b/pkg/services/authn/clients/grafana_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" - "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -141,7 +141,8 @@ func TestGrafana_AuthenticatePassword(t *testing.T) { password: "password", findUser: true, expectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, OrgID: 1, AuthenticatedBy: login.PasswordAuthModule, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, diff --git a/pkg/services/authn/clients/oauth_test.go b/pkg/services/authn/clients/oauth_test.go index 9ae0de62a3a..4f5347428e1 100644 --- a/pkg/services/authn/clients/oauth_test.go +++ b/pkg/services/authn/clients/oauth_test.go @@ -486,7 +486,7 @@ func TestOAuth_Logout(t *testing.T) { } c := ProvideOAuth(authn.ClientWithPrefix("azuread"), tt.cfg, mockService, fakeSocialSvc, &setting.OSSImpl{Cfg: tt.cfg}, featuremgmt.WithFeatures()) - redirect, ok := c.Logout(context.Background(), &authn.Identity{ID: identity.NewTypedIDString(claims.TypeUser, "1")}) + redirect, ok := c.Logout(context.Background(), &authn.Identity{ID: "1", Type: claims.TypeUser}) assert.Equal(t, tt.expectedOK, ok) if tt.expectedOK { diff --git a/pkg/services/authn/clients/password_test.go b/pkg/services/authn/clients/password_test.go index 4719ee2cc9c..e8a90692229 100644 --- a/pkg/services/authn/clients/password_test.go +++ b/pkg/services/authn/clients/password_test.go @@ -4,9 +4,9 @@ import ( "context" "testing" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn/authntest" "github.com/grafana/grafana/pkg/services/loginattempt/loginattempttest" @@ -30,16 +30,16 @@ func TestPassword_AuthenticatePassword(t *testing.T) { username: "test", password: "test", req: &authn.Request{}, - clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}}}, - expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:1")}, + clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: "1", Type: claims.TypeUser}}}, + expectedIdentity: &authn.Identity{ID: "1", Type: claims.TypeUser}, }, { desc: "should success when found in second client", username: "test", password: "test", req: &authn.Request{}, - clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedErr: errIdentityNotFound}, authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2")}}}, - expectedIdentity: &authn.Identity{ID: identity.MustParseTypedID("user:2")}, + clients: []authn.PasswordClient{authntest.FakePasswordClient{ExpectedErr: errIdentityNotFound}, authntest.FakePasswordClient{ExpectedIdentity: &authn.Identity{ID: "2", Type: claims.TypeUser}}}, + expectedIdentity: &authn.Identity{ID: "2", Type: claims.TypeUser}, }, { desc: "should fail for empty password", diff --git a/pkg/services/authn/clients/proxy.go b/pkg/services/authn/clients/proxy.go index 7c838d119ef..90c44bafeee 100644 --- a/pkg/services/authn/clients/proxy.go +++ b/pkg/services/authn/clients/proxy.go @@ -13,8 +13,8 @@ import ( "time" "github.com/grafana/authlib/claims" + "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/infra/remotecache" "github.com/grafana/grafana/pkg/services/authn" @@ -120,13 +120,14 @@ func (c *Proxy) retrieveIDFromCache(ctx context.Context, cacheKey string, r *aut return nil, err } - uid, err := strconv.ParseInt(string(entry), 10, 64) + _, err = strconv.ParseInt(string(entry), 10, 64) if err != nil { return nil, fmt.Errorf("failed to parse user id from cache: %w - entry: %s", err, string(entry)) } return &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, uid), + ID: string(entry), + Type: claims.TypeUser, OrgID: r.OrgID, // FIXME: This does not match the actual auth module used, but should not have any impact // Maybe caching the auth module used with the user ID would be a good idea @@ -151,13 +152,13 @@ func (c *Proxy) Hook(ctx context.Context, id *authn.Identity, r *authn.Request) return nil } - if !id.ID.IsType(claims.TypeUser) { + if !id.IsIdentityType(claims.TypeUser) { return nil } - internalId, err := id.ID.ParseInt() + internalId, err := id.GetInternalID() if err != nil { - c.log.Warn("Failed to cache proxy user", "error", err, "userId", id.ID.ID(), "err", err) + c.log.Warn("Failed to cache proxy user", "error", err, "userId", id.GetID(), "err", err) return nil } diff --git a/pkg/services/authn/clients/proxy_test.go b/pkg/services/authn/clients/proxy_test.go index 7aa39efaa71..24d5004f441 100644 --- a/pkg/services/authn/clients/proxy_test.go +++ b/pkg/services/authn/clients/proxy_test.go @@ -12,7 +12,6 @@ import ( "github.com/stretchr/testify/require" "github.com/grafana/authlib/claims" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn/authntest" "github.com/grafana/grafana/pkg/setting" @@ -204,8 +203,6 @@ func TestProxy_Hook(t *testing.T) { proxyFieldRole: "X-Role", } cache := &fakeCache{data: make(map[string][]byte)} - userId := int64(1) - userID := identity.NewTypedID(claims.TypeUser, userId) // withRole creates a test case for a user with a specific role. withRole := func(role string) func(t *testing.T) { @@ -214,7 +211,8 @@ func TestProxy_Hook(t *testing.T) { c, err := ProvideProxy(cfg, cache, authntest.MockProxyClient{}) require.NoError(t, err) userIdentity := &authn.Identity{ - ID: userID, + ID: "1", + Type: claims.TypeUser, ClientParams: authn.ClientParams{ CacheAuthProxyKey: cacheKey, }, @@ -230,7 +228,7 @@ func TestProxy_Hook(t *testing.T) { err = c.Hook(context.Background(), userIdentity, userReq) assert.NoError(t, err) expectedCache := map[string][]byte{ - cacheKey: []byte(fmt.Sprintf("%d", userId)), + cacheKey: []byte("1"), fmt.Sprintf("%s:%s", proxyCachePrefix, "johndoe"): []byte(fmt.Sprintf("users:johndoe-%s", role)), } assert.Equal(t, expectedCache, cache.data) diff --git a/pkg/services/authn/clients/render.go b/pkg/services/authn/clients/render.go index 6ed57114af0..e63507ff710 100644 --- a/pkg/services/authn/clients/render.go +++ b/pkg/services/authn/clients/render.go @@ -2,11 +2,11 @@ package clients import ( "context" + "strconv" "time" "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/errutil" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -44,7 +44,8 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide if renderUsr.UserID <= 0 { return &authn.Identity{ - ID: identity.NewTypedID(claims.TypeRenderService, 0), + ID: "0", + Type: claims.TypeRenderService, OrgID: renderUsr.OrgID, OrgRoles: map[int64]org.RoleType{renderUsr.OrgID: org.RoleType(renderUsr.OrgRole)}, ClientParams: authn.ClientParams{SyncPermissions: true}, @@ -54,7 +55,8 @@ func (c *Render) Authenticate(ctx context.Context, r *authn.Request) (*authn.Ide } return &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, renderUsr.UserID), + ID: strconv.FormatInt(renderUsr.UserID, 10), + Type: claims.TypeUser, LastSeenAt: time.Now(), AuthenticatedBy: login.RenderModule, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, diff --git a/pkg/services/authn/clients/render_test.go b/pkg/services/authn/clients/render_test.go index 7ab176e2aeb..c30bdfe48d2 100644 --- a/pkg/services/authn/clients/render_test.go +++ b/pkg/services/authn/clients/render_test.go @@ -7,9 +7,9 @@ import ( "time" "github.com/golang/mock/gomock" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/services/org" @@ -36,7 +36,8 @@ func TestRender_Authenticate(t *testing.T) { }, }, expectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("render:0"), + ID: "0", + Type: claims.TypeRenderService, OrgID: 1, OrgRoles: map[int64]org.RoleType{1: org.RoleViewer}, AuthenticatedBy: login.RenderModule, @@ -57,7 +58,8 @@ func TestRender_Authenticate(t *testing.T) { }, }, expectedIdentity: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, AuthenticatedBy: login.RenderModule, ClientParams: authn.ClientParams{FetchSyncedUser: true, SyncPermissions: true}, }, diff --git a/pkg/services/authn/clients/session.go b/pkg/services/authn/clients/session.go index 9945612c79f..43ce7dfddcf 100644 --- a/pkg/services/authn/clients/session.go +++ b/pkg/services/authn/clients/session.go @@ -4,10 +4,10 @@ import ( "context" "errors" "net/url" + "strconv" "time" "github.com/grafana/authlib/claims" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn" @@ -59,7 +59,8 @@ func (s *Session) Authenticate(ctx context.Context, r *authn.Request) (*authn.Id } ident := &authn.Identity{ - ID: identity.NewTypedID(claims.TypeUser, token.UserId), + ID: strconv.FormatInt(token.UserId, 10), + Type: claims.TypeUser, SessionToken: token, ClientParams: authn.ClientParams{ FetchSyncedUser: true, diff --git a/pkg/services/authn/clients/session_test.go b/pkg/services/authn/clients/session_test.go index 28b14b5bf41..e290f55441f 100644 --- a/pkg/services/authn/clients/session_test.go +++ b/pkg/services/authn/clients/session_test.go @@ -6,10 +6,10 @@ import ( "testing" "time" + "github.com/grafana/authlib/claims" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/models/usertoken" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/auth/authtest" @@ -97,7 +97,8 @@ func TestSession_Authenticate(t *testing.T) { }, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, wantID: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, SessionToken: validToken, ClientParams: authn.ClientParams{ SyncPermissions: true, @@ -130,7 +131,9 @@ func TestSession_Authenticate(t *testing.T) { }, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, wantID: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, + SessionToken: validToken, ClientParams: authn.ClientParams{ SyncPermissions: true, @@ -149,7 +152,8 @@ func TestSession_Authenticate(t *testing.T) { }, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, wantID: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, AuthID: "1", AuthenticatedBy: "oauth_azuread", SessionToken: validToken, @@ -171,7 +175,8 @@ func TestSession_Authenticate(t *testing.T) { }, args: args{r: &authn.Request{HTTPRequest: validHTTPReq}}, wantID: &authn.Identity{ - ID: identity.MustParseTypedID("user:1"), + ID: "1", + Type: claims.TypeUser, SessionToken: validToken, ClientParams: authn.ClientParams{ diff --git a/pkg/services/authn/identity.go b/pkg/services/authn/identity.go index aa16ebb636f..776283bf92d 100644 --- a/pkg/services/authn/identity.go +++ b/pkg/services/authn/identity.go @@ -2,6 +2,7 @@ package authn import ( "fmt" + "strconv" "time" "github.com/grafana/authlib/authn" @@ -21,10 +22,11 @@ var _ identity.Requester = (*Identity)(nil) type Identity struct { // ID is the unique identifier for the entity in the Grafana database. - // If the entity is not found in the DB or this entity is non-persistent, this field will be empty. - ID identity.TypedID + ID string // UID is a unique identifier stored for the entity in Grafana database. Not all entities support uid so it can be empty. - UID identity.TypedID + UID string + // Type is the IdentityType of entity. + Type claims.IdentityType // OrgID is the active organization for the entity. OrgID int64 // OrgName is the name of the active organization. @@ -90,17 +92,22 @@ func (i *Identity) GetIdentity() claims.IdentityClaims { // GetRawIdentifier implements Requester. func (i *Identity) GetRawIdentifier() string { - return i.UID.ID() + return i.UID } // GetInternalID implements Requester. func (i *Identity) GetInternalID() (int64, error) { - return i.ID.UserID() + return identity.IntIdentifier(i.GetID()) } // GetIdentityType implements Requester. func (i *Identity) GetIdentityType() claims.IdentityType { - return i.UID.Type() + return i.Type +} + +// GetIdentityType implements Requester. +func (i *Identity) IsIdentityType(expected ...claims.IdentityType) bool { + return claims.IsIdentityType(i.GetIdentityType(), expected...) } // GetExtra implements identity.Requester. @@ -125,12 +132,12 @@ func (i *Identity) GetName() string { return i.Name } -func (i *Identity) GetID() identity.TypedID { - return i.ID +func (i *Identity) GetID() string { + return identity.NewTypedIDString(i.Type, i.ID) } func (i *Identity) GetUID() string { - return i.UID.String() + return identity.NewTypedIDString(i.Type, i.UID) } func (i *Identity) GetAuthID() string { @@ -142,14 +149,14 @@ func (i *Identity) GetAuthenticatedBy() string { } func (i *Identity) GetCacheKey() string { - id := i.GetID().ID() + id := i.ID if !i.HasUniqueId() { // Hack use the org role as id for identities that do not have a unique id // e.g. anonymous and render key. id = string(i.GetOrgRole()) } - return fmt.Sprintf("%d-%s-%s", i.GetOrgID(), i.GetID().Type(), id) + return fmt.Sprintf("%d-%s-%s", i.GetOrgID(), i.Type, id) } func (i *Identity) GetDisplayName() string { @@ -242,10 +249,7 @@ func (i *Identity) HasRole(role org.RoleType) bool { } func (i *Identity) HasUniqueId() bool { - typ := i.GetID().Type() - return typ == claims.TypeUser || - typ == claims.TypeServiceAccount || - typ == claims.TypeAPIKey + return i.IsIdentityType(claims.TypeUser, claims.TypeAPIKey, claims.TypeServiceAccount) } func (i *Identity) IsAuthenticatedBy(providers ...string) bool { @@ -273,31 +277,31 @@ func (i *Identity) SignedInUser() *user.SignedInUser { AuthID: i.AuthID, AuthenticatedBy: i.AuthenticatedBy, IsGrafanaAdmin: i.GetIsGrafanaAdmin(), - IsAnonymous: i.ID.IsType(claims.TypeAnonymous), + IsAnonymous: i.IsIdentityType(claims.TypeAnonymous), IsDisabled: i.IsDisabled, HelpFlags1: i.HelpFlags1, LastSeenAt: i.LastSeenAt, Teams: i.Teams, Permissions: i.Permissions, IDToken: i.IDToken, - FallbackType: i.ID.Type(), + FallbackType: i.Type, } - if i.ID.IsType(claims.TypeAPIKey) { - id, _ := i.ID.ParseInt() + if i.IsIdentityType(claims.TypeAPIKey) { + id, _ := i.GetInternalID() u.ApiKeyID = id } else { - id, _ := i.ID.UserID() + id, _ := i.GetInternalID() u.UserID = id - u.UserUID = i.UID.ID() - u.IsServiceAccount = i.ID.IsType(claims.TypeServiceAccount) + u.UserUID = i.UID + u.IsServiceAccount = i.IsIdentityType(claims.TypeServiceAccount) } return u } func (i *Identity) ExternalUserInfo() login.ExternalUserInfo { - id, _ := i.ID.UserID() + id, _ := strconv.ParseInt(i.ID, 10, 64) return login.ExternalUserInfo{ OAuthToken: i.OAuthToken, AuthModule: i.AuthenticatedBy, diff --git a/pkg/services/authz/server.go b/pkg/services/authz/server.go index 42f0523df98..c9a4fb16c84 100644 --- a/pkg/services/authz/server.go +++ b/pkg/services/authz/server.go @@ -5,7 +5,6 @@ import ( authzv1 "github.com/grafana/authlib/authz/proto/v1" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -57,16 +56,11 @@ func (s *legacyServer) Read(ctx context.Context, req *authzv1.ReadRequest) (*aut ctxLogger := s.logger.FromContext(ctx) ctxLogger.Debug("Read", "action", action, "subject", subject, "stackID", stackID) - var err error - opts := accesscontrol.SearchOptions{Action: action} - if subject != "" { - opts.TypedID, err = identity.ParseTypedID(subject) - if err != nil { - return nil, err - } - } - - permissions, err := s.acSvc.SearchUserPermissions(ctx, stackID, opts) + permissions, err := s.acSvc.SearchUserPermissions( + ctx, + stackID, + accesscontrol.SearchOptions{Action: action, TypedID: subject}, + ) if err != nil { ctxLogger.Error("failed to search user permissions", "error", err) return nil, tracing.Errorf(span, "failed to search user permissions: %w", err) diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index d5ea06013fa..a30f560b414 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -155,22 +155,21 @@ func (h *ContextHandler) addIDHeaderEndOfRequestFunc(ident identity.Requester) w return } - id := ident.GetID() - if !identity.IsIdentityType( - id, + id, _ := ident.GetInternalID() + if !ident.IsIdentityType( claims.TypeUser, claims.TypeServiceAccount, claims.TypeAPIKey, - ) || id.ID() == "0" { + ) || id == 0 { return } - if _, ok := h.Cfg.IDResponseHeaderNamespaces[id.Type().String()]; !ok { + if _, ok := h.Cfg.IDResponseHeaderNamespaces[string(ident.GetIdentityType())]; !ok { return } headerName := fmt.Sprintf("%s-Identity-Id", h.Cfg.IDResponseHeaderPrefix) - w.Header().Add(headerName, id.String()) + w.Header().Add(headerName, ident.GetID()) } } diff --git a/pkg/services/contexthandler/contexthandler_test.go b/pkg/services/contexthandler/contexthandler_test.go index 27f3e1cd03c..1a0458a48fe 100644 --- a/pkg/services/contexthandler/contexthandler_test.go +++ b/pkg/services/contexthandler/contexthandler_test.go @@ -44,7 +44,7 @@ func TestContextHandler(t *testing.T) { }) t.Run("should set identity on successful authentication", func(t *testing.T) { - id := &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, 1), OrgID: 1} + id := &authn.Identity{ID: "1", Type: claims.TypeUser, OrgID: 1} handler := contexthandler.ProvideService( setting.NewCfg(), tracing.InitializeTracerForTest(), @@ -69,7 +69,7 @@ func TestContextHandler(t *testing.T) { }) t.Run("should not set IsSignedIn on anonymous identity", func(t *testing.T) { - identity := &authn.Identity{ID: identity.AnonymousTypedID, OrgID: 1} + identity := &authn.Identity{ID: "0", Type: claims.TypeAnonymous, OrgID: 1} handler := contexthandler.ProvideService( setting.NewCfg(), tracing.InitializeTracerForTest(), @@ -146,10 +146,13 @@ func TestContextHandler(t *testing.T) { t.Run("id response headers", func(t *testing.T) { run := func(cfg *setting.Cfg, id string) *http.Response { + typ, i, err := identity.ParseTypeAndID(id) + require.NoError(t, err) + handler := contexthandler.ProvideService( cfg, tracing.InitializeTracerForTest(), - &authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: identity.MustParseTypedID(id)}}, + &authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: i, Type: typ}}, ) server := webtest.NewServer(t, routing.NewRouteRegister()) diff --git a/pkg/services/dashboards/database/database.go b/pkg/services/dashboards/database/database.go index 4b85d539a8a..8e91674cb35 100644 --- a/pkg/services/dashboards/database/database.go +++ b/pkg/services/dashboards/database/database.go @@ -879,7 +879,7 @@ func (d *dashboardStore) FindDashboards(ctx context.Context, query *dashboards.F } // only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users - if query.SignedInUser == nil || query.SignedInUser.GetID().Type() != claims.TypeServiceAccount { + if query.SignedInUser == nil || !query.SignedInUser.IsIdentityType(claims.TypeServiceAccount) { filters = append(filters, searchstore.K6FolderFilter{}) } diff --git a/pkg/services/dashboards/service/dashboard_service.go b/pkg/services/dashboards/service/dashboard_service.go index d3dc27e2b28..f1e40b2f98a 100644 --- a/pkg/services/dashboards/service/dashboard_service.go +++ b/pkg/services/dashboards/service/dashboard_service.go @@ -489,11 +489,11 @@ func (dr *DashboardServiceImpl) setDefaultPermissions(ctx context.Context, dto * inFolder := dash.FolderID > 0 var permissions []accesscontrol.SetResourcePermissionCommand - if !provisioned { - userID, err := identity.IntIdentifier(dto.User.GetID()) + if !provisioned && dto.User.IsIdentityType(claims.TypeUser) { + userID, err := dto.User.GetInternalID() if err != nil { dr.log.Error("Could not make user admin", "dashboard", dash.Title, "id", dto.User.GetID(), "error", err) - } else if identity.IsIdentityType(dto.User.GetID(), claims.TypeUser) { + } else { permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(), }) @@ -525,11 +525,11 @@ func (dr *DashboardServiceImpl) setDefaultFolderPermissions(ctx context.Context, inFolder := f.ParentUID != "" var permissions []accesscontrol.SetResourcePermissionCommand - if !provisioned { - userID, err := identity.IntIdentifier(cmd.SignedInUser.GetID()) + if !provisioned && cmd.SignedInUser.IsIdentityType(claims.TypeUser) { + userID, err := cmd.SignedInUser.GetInternalID() if err != nil { dr.log.Error("Could not make user admin", "folder", cmd.Title, "id", cmd.SignedInUser.GetID()) - } else if identity.IsIdentityType(cmd.SignedInUser.GetID(), claims.TypeUser) { + } else { permissions = append(permissions, accesscontrol.SetResourcePermissionCommand{ UserID: userID, Permission: dashboardaccess.PERMISSION_ADMIN.String(), }) diff --git a/pkg/services/dashboardsnapshots/database/database.go b/pkg/services/dashboardsnapshots/database/database.go index bcc7c9c32e8..728883a9408 100644 --- a/pkg/services/dashboardsnapshots/database/database.go +++ b/pkg/services/dashboardsnapshots/database/database.go @@ -5,7 +5,6 @@ import ( "time" "github.com/grafana/authlib/claims" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" @@ -124,9 +123,9 @@ func (d *DashboardSnapshotStore) SearchDashboardSnapshots(ctx context.Context, q } var userID int64 - if identity.IsIdentityType(query.SignedInUser.GetID(), claims.TypeUser, claims.TypeServiceAccount) { + if query.SignedInUser.IsIdentityType(claims.TypeUser, claims.TypeServiceAccount) { var err error - userID, err = identity.UserIdentifier(query.SignedInUser.GetID()) + userID, err = query.SignedInUser.GetInternalID() if err != nil { return err } diff --git a/pkg/services/folder/folderimpl/sqlstore.go b/pkg/services/folder/folderimpl/sqlstore.go index d2b0c7a2bc3..c4464087840 100644 --- a/pkg/services/folder/folderimpl/sqlstore.go +++ b/pkg/services/folder/folderimpl/sqlstore.go @@ -323,7 +323,7 @@ func (ss *sqlStore) GetChildren(ctx context.Context, q folder.GetChildrenQuery) } // only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users - if q.SignedInUser == nil || q.SignedInUser.GetID().Type() != claims.TypeServiceAccount { + if q.SignedInUser == nil || !q.SignedInUser.IsIdentityType(claims.TypeServiceAccount) { sql.WriteString(" AND uid != ?") args = append(args, accesscontrol.K6FolderUID) } @@ -484,7 +484,7 @@ func (ss *sqlStore) GetFolders(ctx context.Context, q getFoldersQuery) ([]*folde } // only list k6 folders when requested by a service account - prevents showing k6 folders in the UI for users - if q.SignedInUser == nil || q.SignedInUser.GetID().Type() != claims.TypeServiceAccount { + if q.SignedInUser == nil || !q.SignedInUser.IsIdentityType(claims.TypeServiceAccount) { s.WriteString(" AND f0.uid != ? AND (f0.parent_uid != ? OR f0.parent_uid IS NULL)") args = append(args, accesscontrol.K6FolderUID, accesscontrol.K6FolderUID) } diff --git a/pkg/services/live/live.go b/pkg/services/live/live.go index 18eb4296b10..5ea854ac625 100644 --- a/pkg/services/live/live.go +++ b/pkg/services/live/live.go @@ -284,9 +284,10 @@ func ProvideService(plugCtxProvider *plugincontext.Provider, cfg *setting.Cfg, r g.websocketHandler = func(ctx *contextmodel.ReqContext) { user := ctx.SignedInUser + id, _ := user.GetInternalID() // Centrifuge expects Credentials in context with a current user ID. cred := ¢rifuge.Credentials{ - UserID: user.GetID().ID(), + UserID: strconv.FormatInt(id, 10), } newCtx := centrifuge.SetCredentials(ctx.Req.Context(), cred) newCtx = livecontext.SetContextSignedUser(newCtx, user) diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 27aca213e35..4cc0d4ab611 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -76,7 +76,8 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceU return toNamespaceErrorResponse(err) } - userNamespace, id := c.SignedInUser.GetID().Type(), c.SignedInUser.GetID().ID() + id, _ := c.SignedInUser.GetInternalID() + userNamespace := c.SignedInUser.GetIdentityType() var loggerCtx = []any{ "identity", id, @@ -294,7 +295,8 @@ func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Res for groupKey, rules := range configs { folder, ok := namespaceMap[groupKey.NamespaceUID] if !ok { - userNamespace, id := c.SignedInUser.GetID().Type(), c.SignedInUser.GetID().ID() + id, _ := c.SignedInUser.GetInternalID() + userNamespace := c.SignedInUser.GetIdentityType() srv.log.Error("Namespace not visible to the user", "user", id, "userNamespace", userNamespace, "namespace", groupKey.NamespaceUID) continue } @@ -370,7 +372,9 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey var finalChanges *store.GroupDelta var dbConfig *ngmodels.AlertConfiguration err := srv.xactManager.InTransaction(c.Req.Context(), func(tranCtx context.Context) error { - userNamespace, id := c.SignedInUser.GetID().Type(), c.SignedInUser.GetID().ID() + id, _ := c.SignedInUser.GetInternalID() + userNamespace := c.SignedInUser.GetIdentityType() + logger := srv.log.New("namespace_uid", groupKey.NamespaceUID, "group", groupKey.RuleGroup, "org_id", groupKey.OrgID, "user_id", id, "userNamespace", userNamespace) groupChanges, err := store.CalculateChanges(tranCtx, srv.store, groupKey, rules) diff --git a/pkg/services/oauthtoken/oauth_token.go b/pkg/services/oauthtoken/oauth_token.go index cc736d959b4..ea1572f7ea9 100644 --- a/pkg/services/oauthtoken/oauth_token.go +++ b/pkg/services/oauthtoken/oauth_token.go @@ -95,12 +95,12 @@ func (o *Service) HasOAuthEntry(ctx context.Context, usr identity.Requester) (*l return nil, false, nil } - if !identity.IsIdentityType(usr.GetID(), claims.TypeUser) { + if !usr.IsIdentityType(claims.TypeUser) { return nil, false, nil } ctxLogger := logger.FromContext(ctx) - userID, err := identity.UserIdentifier(usr.GetID()) + userID, err := usr.GetInternalID() if err != nil { ctxLogger.Error("Failed to convert user id to int", "id", usr.GetID(), "error", err) return nil, false, err @@ -136,12 +136,12 @@ func (o *Service) TryTokenRefresh(ctx context.Context, usr identity.Requester) e return nil } - if !identity.IsIdentityType(usr.GetID(), claims.TypeUser) { + if !usr.IsIdentityType(claims.TypeUser) { ctxLogger.Warn("Can only refresh OAuth tokens for users", "id", usr.GetID()) return nil } - userID, err := identity.UserIdentifier(usr.GetID()) + userID, err := usr.GetInternalID() if err != nil { ctxLogger.Warn("Failed to convert user id to int", "id", usr.GetID(), "error", err) return nil diff --git a/pkg/services/oauthtoken/oauth_token_test.go b/pkg/services/oauthtoken/oauth_token_test.go index aee92134f95..59117a4aacc 100644 --- a/pkg/services/oauthtoken/oauth_token_test.go +++ b/pkg/services/oauthtoken/oauth_token_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/grafana/authlib/claims" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -174,13 +175,13 @@ func TestService_TryTokenRefresh(t *testing.T) { { desc: "should skip sync when identity is not a user", setup: func(env *environment) { - env.identity = &authn.Identity{ID: identity.MustParseTypedID("service-account:1")} + env.identity = &authn.Identity{ID: "1", Type: claims.TypeServiceAccount} }, }, { desc: "should skip token refresh and return nil if namespace and id cannot be converted to user ID", setup: func(env *environment) { - env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:invalidIdentifierFormat")} + env.identity = &authn.Identity{ID: "invalid", Type: claims.TypeUser} }, }, { @@ -203,28 +204,29 @@ func TestService_TryTokenRefresh(t *testing.T) { env.identity = &authn.Identity{ AuthenticatedBy: login.GenericOAuthModule, - ID: identity.MustParseTypedID("user:1234"), + ID: "1234", + Type: claims.TypeUser, } }, }, { desc: "should skip token refresh if the expiration check has already been cached", setup: func(env *environment) { - env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")} + env.identity = &authn.Identity{ID: "1234", Type: claims.TypeUser} env.cache.Set("oauth-refresh-token-1234", true, 1*time.Minute) }, }, { desc: "should skip token refresh if there's an unexpected error while looking up the user oauth entry, additionally, no error should be returned", setup: func(env *environment) { - env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")} + env.identity = &authn.Identity{ID: "1234", Type: claims.TypeUser} env.authInfoService.ExpectedError = errors.New("some error") }, }, { desc: "should skip token refresh if the user doesn't have an oauth entry", setup: func(env *environment) { - env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")} + env.identity = &authn.Identity{ID: "1234", Type: claims.TypeUser} env.authInfoService.ExpectedUserAuth = &login.UserAuth{ AuthModule: login.SAMLAuthModule, } @@ -233,7 +235,7 @@ func TestService_TryTokenRefresh(t *testing.T) { { desc: "should do token refresh if access token or id token have not expired yet", setup: func(env *environment) { - env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")} + env.identity = &authn.Identity{ID: "1234", Type: claims.TypeUser} env.authInfoService.ExpectedUserAuth = &login.UserAuth{ AuthModule: login.GenericOAuthModule, } @@ -242,7 +244,7 @@ func TestService_TryTokenRefresh(t *testing.T) { { desc: "should skip token refresh when no oauth provider was found", setup: func(env *environment) { - env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")} + env.identity = &authn.Identity{ID: "1234", Type: claims.TypeUser} env.authInfoService.ExpectedUserAuth = &login.UserAuth{ AuthModule: login.GenericOAuthModule, OAuthIdToken: EXPIRED_JWT, @@ -252,7 +254,7 @@ func TestService_TryTokenRefresh(t *testing.T) { { desc: "should skip token refresh when oauth provider token handling is disabled (UseRefreshToken is false)", setup: func(env *environment) { - env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")} + env.identity = &authn.Identity{ID: "1234", Type: claims.TypeUser} env.authInfoService.ExpectedUserAuth = &login.UserAuth{ AuthModule: login.GenericOAuthModule, OAuthIdToken: EXPIRED_JWT, @@ -265,7 +267,7 @@ func TestService_TryTokenRefresh(t *testing.T) { { desc: "should skip token refresh when there is no refresh token", setup: func(env *environment) { - env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")} + env.identity = &authn.Identity{ID: "1234", Type: claims.TypeUser} env.authInfoService.ExpectedUserAuth = &login.UserAuth{ AuthModule: login.GenericOAuthModule, OAuthIdToken: EXPIRED_JWT, @@ -285,7 +287,7 @@ func TestService_TryTokenRefresh(t *testing.T) { Expiry: time.Now().Add(-time.Hour), TokenType: "Bearer", } - env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")} + env.identity = &authn.Identity{ID: "1234", Type: claims.TypeUser} env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ UseRefreshToken: true, } @@ -310,7 +312,7 @@ func TestService_TryTokenRefresh(t *testing.T) { Expiry: time.Now().Add(time.Hour), TokenType: "Bearer", } - env.identity = &authn.Identity{ID: identity.MustParseTypedID("user:1234")} + env.identity = &authn.Identity{ID: "1234", Type: claims.TypeUser} env.socialService.ExpectedAuthInfoProvider = &social.OAuthInfo{ UseRefreshToken: true, } diff --git a/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go b/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go index e15ae29e7db..214b1305979 100644 --- a/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go +++ b/pkg/services/pluginsintegration/clientmiddleware/user_header_middleware.go @@ -6,7 +6,6 @@ import ( "github.com/grafana/authlib/claims" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/util/proxyutil" @@ -36,7 +35,7 @@ func (m *UserHeaderMiddleware) applyUserHeader(ctx context.Context, h backend.Fo } h.DeleteHTTPHeader(proxyutil.UserHeaderName) - if !identity.IsIdentityType(reqCtx.SignedInUser.GetID(), claims.TypeAnonymous) { + if !reqCtx.SignedInUser.IsIdentityType(claims.TypeAnonymous) { h.SetHTTPHeader(proxyutil.UserHeaderName, reqCtx.SignedInUser.GetLogin()) } } diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go index a14f05993bd..a0db940a2bc 100644 --- a/pkg/services/serviceaccounts/api/api.go +++ b/pkg/services/serviceaccounts/api/api.go @@ -8,7 +8,6 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/middleware/requestmeta" "github.com/grafana/grafana/pkg/services/accesscontrol" @@ -100,8 +99,8 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *contextmodel.ReqContext) } if api.cfg.RBAC.PermissionsOnCreation("service-account") { - if identity.IsIdentityType(c.SignedInUser.GetID(), claims.TypeUser) { - userID, err := c.SignedInUser.GetID().ParseInt() + if c.SignedInUser.IsIdentityType(claims.TypeUser) { + userID, err := c.SignedInUser.GetInternalID() if err != nil { return response.Error(http.StatusInternalServerError, "Failed to parse user id", err) } diff --git a/pkg/services/team/teamapi/team.go b/pkg/services/team/teamapi/team.go index 0fc23039fc0..71d90504c38 100644 --- a/pkg/services/team/teamapi/team.go +++ b/pkg/services/team/teamapi/team.go @@ -49,20 +49,12 @@ func (tapi *TeamAPI) createTeam(c *contextmodel.ReqContext) response.Response { // if the request is authenticated using API tokens // the SignedInUser is an empty struct therefore // an additional check whether it is an actual user is required - namespace, identifier := c.SignedInUser.GetID().Type(), c.SignedInUser.GetID().ID() - switch namespace { - case claims.TypeUser: - userID, err := strconv.ParseInt(identifier, 10, 64) - if err != nil { - c.Logger.Error("Could not add creator to team because user id is not a number", "error", err) - break - } + if c.SignedInUser.IsIdentityType(claims.TypeUser) { + userID, _ := c.SignedInUser.GetInternalID() if err := addOrUpdateTeamMember(c.Req.Context(), tapi.teamPermissionsService, userID, c.SignedInUser.GetOrgID(), t.ID, dashboardaccess.PERMISSION_ADMIN.String()); err != nil { c.Logger.Error("Could not add creator to team", "error", err) } - default: - c.Logger.Warn("Could not add creator to team because is not a real user") } return response.JSON(http.StatusOK, &util.DynMap{ diff --git a/pkg/services/user/identity.go b/pkg/services/user/identity.go index e9b0a4c2011..ece7ed08d3c 100644 --- a/pkg/services/user/identity.go +++ b/pkg/services/user/identity.go @@ -76,16 +76,9 @@ func (u *SignedInUser) GetRawIdentifier() string { return u.UserUID } -// Deprecated: use GetUID +// GetInternalID implements Requester. func (u *SignedInUser) GetInternalID() (int64, error) { - switch { - case u.ApiKeyID != 0: - return u.ApiKeyID, nil - case u.IsAnonymous: - return 0, nil - default: - } - return u.UserID, nil + return identity.IntIdentifier(u.GetID()) } // GetIdentityType implements Requester. @@ -105,6 +98,11 @@ func (u *SignedInUser) GetIdentityType() claims.IdentityType { return u.FallbackType } +// IsIdentityType implements Requester. +func (u *SignedInUser) IsIdentityType(expected ...claims.IdentityType) bool { + return claims.IsIdentityType(u.GetIdentityType(), expected...) +} + // GetName implements identity.Requester. func (u *SignedInUser) GetName() string { return u.Name @@ -183,7 +181,7 @@ func (u *SignedInUser) GetAllowedKubernetesNamespace() string { // GetCacheKey returns a unique key for the entity. // Add an extra prefix to avoid collisions with other caches func (u *SignedInUser) GetCacheKey() string { - typ, id := u.GetID().Type(), u.GetID().ID() + typ, id := u.getTypeAndID() if !u.HasUniqueId() { // Hack use the org role as id for identities that do not have a unique id // e.g. anonymous and render key. @@ -258,7 +256,7 @@ func (u *SignedInUser) GetOrgRole() identity.RoleType { } // GetID returns namespaced id for the entity -func (u *SignedInUser) GetID() identity.TypedID { +func (u *SignedInUser) GetID() string { ns, id := u.getTypeAndID() return identity.NewTypedIDString(ns, id) } diff --git a/pkg/services/user/userimpl/verifier.go b/pkg/services/user/userimpl/verifier.go index 1569ba46285..ad681e52845 100644 --- a/pkg/services/user/userimpl/verifier.go +++ b/pkg/services/user/userimpl/verifier.go @@ -5,11 +5,11 @@ import ( "errors" "fmt" "net/mail" + "strconv" "time" "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/apimachinery/errutil" - "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/notifications" @@ -154,6 +154,6 @@ func (s *Verifier) Complete(ctx context.Context, cmd user.CompleteEmailVerifyCom // remove the current token, so a new one can be generated with correct values. return s.is.RemoveIDToken( ctx, - &authn.Identity{ID: identity.NewTypedID(claims.TypeUser, usr.ID), OrgID: usr.OrgID}, + &authn.Identity{ID: strconv.FormatInt(usr.ID, 10), Type: claims.TypeUser, OrgID: usr.OrgID}, ) } diff --git a/pkg/storage/unified/resource/grpc/authenticator.go b/pkg/storage/unified/resource/grpc/authenticator.go index 23322c998af..10f3c656508 100644 --- a/pkg/storage/unified/resource/grpc/authenticator.go +++ b/pkg/storage/unified/resource/grpc/authenticator.go @@ -89,21 +89,21 @@ func (f *Authenticator) decodeMetadata(ctx context.Context, meta metadata.MD) (i return user, nil } - ns, err := identity.ParseTypedID(getter(mdUserID)) + typ, id, err := identity.ParseTypeAndID(getter(mdUserID)) if err != nil { return nil, fmt.Errorf("invalid user id: %w", err) } - user.Type = ns.Type() - user.UserID, err = ns.ParseInt() + user.Type = typ + user.UserID, err = strconv.ParseInt(id, 10, 64) if err != nil { return nil, fmt.Errorf("invalid user id: %w", err) } - ns, err = identity.ParseTypedID(getter(mdUserUID)) + _, id, err = identity.ParseTypeAndID(getter(mdUserUID)) if err != nil { return nil, fmt.Errorf("invalid user id: %w", err) } - user.UserUID = ns.ID() + user.UserUID = id user.OrgName = getter(mdOrgName) user.OrgID, err = strconv.ParseInt(getter(mdOrgID), 10, 64) @@ -145,12 +145,14 @@ func wrapContext(ctx context.Context) (context.Context, error) { } func encodeIdentityInMetadata(user identity.Requester) metadata.MD { + id, _ := user.GetInternalID() + return metadata.Pairs( // This should be everything needed to recreate the user mdToken, user.GetIDToken(), // Or we can create it directly - mdUserID, user.GetID().String(), + mdUserID, user.GetID(), mdUserUID, user.GetUID(), mdOrgName, user.GetOrgName(), mdOrgID, strconv.FormatInt(user.GetOrgID(), 10), @@ -158,7 +160,7 @@ func encodeIdentityInMetadata(user identity.Requester) metadata.MD { mdLogin, user.GetLogin(), // TODO, Remove after this is deployed to unified storage - "grafana-userid", user.GetID().ID(), + "grafana-userid", strconv.FormatInt(id, 10), "grafana-useruid", user.GetRawIdentifier(), ) } diff --git a/pkg/util/proxyutil/proxyutil.go b/pkg/util/proxyutil/proxyutil.go index 4323d7c3f1b..67957abf608 100644 --- a/pkg/util/proxyutil/proxyutil.go +++ b/pkg/util/proxyutil/proxyutil.go @@ -115,7 +115,7 @@ func ApplyUserHeader(sendUserHeader bool, req *http.Request, user identity.Reque return } - if identity.IsIdentityType(user.GetID(), claims.TypeUser) { + if user.IsIdentityType(claims.TypeUser) { req.Header.Set(UserHeaderName, user.GetLogin()) } } From 8467838bef1b3ff17fddfb0e6599e83eddec7b1e Mon Sep 17 00:00:00 2001 From: Ana Ivanov <38096095+anaivanov@users.noreply.github.com> Date: Tue, 13 Aug 2024 10:54:30 +0200 Subject: [PATCH 036/229] Navigation: Remove grafana-aws-app from Infrastructure navigation (#91524) Remove grafana-aws-app from Infrastructure navigation --- pkg/services/navtree/navtreeimpl/applinks.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index 3f79a6e5c62..e9fb15d950d 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -284,7 +284,6 @@ func (s *ServiceImpl) hasAccessToInclude(c *contextmodel.ReqContext, pluginID st func (s *ServiceImpl) readNavigationSettings() { s.navigationAppConfig = map[string]NavigationAppConfig{ "grafana-k8s-app": {SectionID: navtree.NavIDInfrastructure, SortWeight: 1, Text: "Kubernetes"}, - "grafana-aws-app": {SectionID: navtree.NavIDInfrastructure, SortWeight: 2}, "grafana-app-observability-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightApplication, Text: "Application", Icon: "graph-bar"}, "grafana-lokiexplore-app": {SectionID: navtree.NavIDExplore, SortWeight: 1, Text: "Logs"}, "grafana-exploretraces-app": {SectionID: navtree.NavIDExplore, SortWeight: 2, Text: "Traces"}, From bac68069e070ad6cc03225a9ea484b600e866a2f Mon Sep 17 00:00:00 2001 From: David Harris Date: Tue, 13 Aug 2024 09:57:51 +0100 Subject: [PATCH 037/229] chore: update issue template codeowners (#90925) Adds myself as an additional codeowner for issue templates given the current reliance on ordering for links to request a new plugin from Grafana. This may be a temporary solution prior to being able to raise the requests natively within Grafana. --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a14dacb4487..caf8cb2ac84 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -665,7 +665,7 @@ embed.go @grafana/grafana-as-code # GitHub Workflows and Templates /.github/CODEOWNERS @tolzhabayev -/.github/ISSUE_TEMPLATE/ @torkelo +/.github/ISSUE_TEMPLATE/ @torkelo @sympatheticmoose /.github/PULL_REQUEST_TEMPLATE.md @torkelo /.github/bot.md @torkelo /.github/commands.json @torkelo From 8044cb50f17a021a32e7ac0b83cf8d723457a9ae Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Tue, 13 Aug 2024 11:55:30 +0200 Subject: [PATCH 038/229] Plugins: Plugin details right panel is added. All the details were moved from thee top to the right panel (#90325) * PluginDetailsRight panel is added. All the details were moved from the top to the right panel * Add feature toggle pluginsDetailsRightPanel,Fix build, fix review comments * Fix the typo Co-authored-by: Giuseppe Guerra * hasAccessToExplore * changes after review, add translations * fix betterer * fix betterer * fix css error * fix betterer * fix translation labels, fix position of the right panel * fix the build * add condition to show updatedAt for plugin details * add test to check 2 new fields at plugin details right panel; * change the gap and remove report abuse button from core plugins * add more tests --------- Co-authored-by: Giuseppe Guerra --- .betterer.results | 9 +- .github/CODEOWNERS | 1 + .../feature-toggles/index.md | 1 + .../src/types/featureToggles.gen.ts | 1 + .../PluginSignatureBadge.tsx | 37 +++++++- pkg/services/featuremgmt/registry.go | 7 ++ pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 + pkg/services/featuremgmt/toggles_gen.json | 16 ++++ .../admin/__mocks__/remotePlugin.mock.ts | 1 + public/app/features/plugins/admin/api.ts | 18 +++- .../plugins/admin/components/Changelog.tsx | 44 +++++++++ .../InstallControlsWarning.tsx | 93 ++++++++++++------- .../admin/components/PluginDetailsBody.tsx | 5 + .../PluginDetailsHeaderSignature.tsx | 15 ++- .../admin/components/PluginDetailsPage.tsx | 44 +++++---- .../components/PluginDetailsRightPanel.tsx | 55 +++++++++++ .../admin/components/PluginSubtitle.tsx | 3 +- .../admin/hooks/usePluginDetailsTabs.tsx | 9 ++ .../plugins/admin/hooks/usePluginInfo.tsx | 15 +-- .../admin/pages/PluginDetails.test.tsx | 40 +++++++- public/app/features/plugins/admin/types.ts | 4 + public/locales/en-US/grafana.json | 12 +++ public/locales/pseudo-LOCALE/grafana.json | 12 +++ 24 files changed, 362 insertions(+), 85 deletions(-) create mode 100644 public/app/features/plugins/admin/components/Changelog.tsx create mode 100644 public/app/features/plugins/admin/components/PluginDetailsRightPanel.tsx diff --git a/.betterer.results b/.betterer.results index 6e962654114..576dfc83f2e 100644 --- a/.betterer.results +++ b/.betterer.results @@ -4848,17 +4848,12 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "3"] ], "public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "8"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "9"], - [0, 0, 0, "Styles should be written using objects.", "10"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "5"] ], "public/app/features/plugins/admin/components/InstallControls/index.tsx:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./InstallControlsWarning\`)", "0"], diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index caf8cb2ac84..2f250370c42 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -344,6 +344,7 @@ /packages/grafana-ui/src/components/DataLinks/ @grafana/dataviz-squad /packages/grafana-ui/src/components/DateTimePickers/ @grafana/grafana-frontend-platform /packages/grafana-ui/src/components/Gauge/ @grafana/dataviz-squad +/packages/grafana-ui/src/components/PluginSignatureBadge/ @grafana/plugins-platform-frontend /packages/grafana-ui/src/components/Sparkline/ @grafana/grafana-frontend-platform @grafana/app-o11y-visualizations /packages/grafana-ui/src/components/Table/ @grafana/dataviz-squad /packages/grafana-ui/src/components/Table/SparklineCell.tsx @grafana/dataviz-squad @grafana/app-o11y-visualizations diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 31128abc74b..74f26f97f6f 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -137,6 +137,7 @@ Experimental features might be changed or removed without prior notice. | `lokiPredefinedOperations` | Adds predefined query operations to Loki query editor | | `pluginsFrontendSandbox` | Enables the plugins frontend sandbox | | `frontendSandboxMonitorOnly` | Enables monitor only in the plugin frontend sandbox (if enabled) | +| `pluginsDetailsRightPanel` | Enables right panel for the plugins details page | | `vizAndWidgetSplit` | Split panels between visualizations and widgets | | `awsDatasourcesTempCredentials` | Support temporary security credentials in AWS plugins for Grafana Cloud customers | | `mlExpressions` | Enable support for Machine Learning in server-side expressions | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 84290064f75..e009b3da487 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -77,6 +77,7 @@ export interface FeatureToggles { lokiPredefinedOperations?: boolean; pluginsFrontendSandbox?: boolean; frontendSandboxMonitorOnly?: boolean; + pluginsDetailsRightPanel?: boolean; sqlDatasourceDatabaseSelection?: boolean; recordedQueriesMulti?: boolean; vizAndWidgetSplit?: boolean; diff --git a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx index af8720cca61..403a7c4b408 100644 --- a/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx +++ b/packages/grafana-ui/src/components/PluginSignatureBadge/PluginSignatureBadge.tsx @@ -1,21 +1,37 @@ import { HTMLAttributes } from 'react'; -import { PluginSignatureStatus } from '@grafana/data'; +import { PluginSignatureStatus, PluginSignatureType } from '@grafana/data'; +import { IconName } from '../../types'; import { Badge, BadgeProps } from '../Badge/Badge'; +const SIGNATURE_ICONS: Record = { + [PluginSignatureType.grafana]: 'grafana', + [PluginSignatureType.commercial]: 'shield', + [PluginSignatureType.community]: 'shield', + DEFAULT: 'shield-exclamation', +}; + /** * @public */ export interface PluginSignatureBadgeProps extends HTMLAttributes { status?: PluginSignatureStatus; + signatureType?: PluginSignatureType; + signatureOrg?: string; } /** * @public */ -export const PluginSignatureBadge = ({ status, color, ...otherProps }: PluginSignatureBadgeProps) => { - const display = getSignatureDisplayModel(status); +export const PluginSignatureBadge = ({ + status, + color, + signatureType, + signatureOrg, + ...otherProps +}: PluginSignatureBadgeProps) => { + const display = getSignatureDisplayModel(status, signatureType, signatureOrg); return ( ); @@ -23,16 +39,27 @@ export const PluginSignatureBadge = ({ status, color, ...otherProps }: PluginSig PluginSignatureBadge.displayName = 'PluginSignatureBadge'; -function getSignatureDisplayModel(signature?: PluginSignatureStatus): BadgeProps { +function getSignatureDisplayModel( + signature?: PluginSignatureStatus, + signatureType?: PluginSignatureType, + signatureOrg?: string +): BadgeProps { if (!signature) { signature = PluginSignatureStatus.invalid; } + const signatureIcon = SIGNATURE_ICONS[signatureType || ''] || SIGNATURE_ICONS.DEFAULT; + switch (signature) { case PluginSignatureStatus.internal: return { text: 'Core', color: 'blue', tooltip: 'Core plugin that is bundled with Grafana' }; case PluginSignatureStatus.valid: - return { text: 'Signed', icon: 'lock', color: 'green', tooltip: 'Signed and verified plugin' }; + return { + text: signatureType ? signatureType : 'Signed', + icon: signatureType ? signatureIcon : 'lock', + color: 'green', + tooltip: 'Signed and verified plugin', + }; case PluginSignatureStatus.invalid: return { text: 'Invalid signature', diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 70023e4e489..0426f7fcf4a 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -443,6 +443,13 @@ var ( FrontendOnly: true, Owner: grafanaPluginsPlatformSquad, }, + { + Name: "pluginsDetailsRightPanel", + Description: "Enables right panel for the plugins details page", + Stage: FeatureStageExperimental, + FrontendOnly: true, + Owner: grafanaPluginsPlatformSquad, + }, { Name: "sqlDatasourceDatabaseSelection", Description: "Enables previous SQL data source dataset dropdown behavior", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index edd8800cd30..344650d1c7b 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -58,6 +58,7 @@ extraThemes,experimental,@grafana/grafana-frontend-platform,false,false,true lokiPredefinedOperations,experimental,@grafana/observability-logs,false,false,true pluginsFrontendSandbox,experimental,@grafana/plugins-platform-backend,false,false,true frontendSandboxMonitorOnly,experimental,@grafana/plugins-platform-backend,false,false,true +pluginsDetailsRightPanel,experimental,@grafana/plugins-platform-backend,false,false,true sqlDatasourceDatabaseSelection,preview,@grafana/dataviz-squad,false,false,true recordedQueriesMulti,GA,@grafana/observability-metrics,false,false,false vizAndWidgetSplit,experimental,@grafana/dashboards-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 84381836572..90e3724901d 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -243,6 +243,10 @@ const ( // Enables monitor only in the plugin frontend sandbox (if enabled) FlagFrontendSandboxMonitorOnly = "frontendSandboxMonitorOnly" + // FlagPluginsDetailsRightPanel + // Enables right panel for the plugins details page + FlagPluginsDetailsRightPanel = "pluginsDetailsRightPanel" + // FlagSqlDatasourceDatabaseSelection // Enables previous SQL data source dataset dropdown behavior FlagSqlDatasourceDatabaseSelection = "sqlDatasourceDatabaseSelection" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 931fa9b8524..16585bbf70b 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2038,6 +2038,22 @@ "frontend": true } }, + { + "metadata": { + "name": "pluginsDetailsRightPanel", + "resourceVersion": "1720788722220", + "creationTimestamp": "2024-07-12T08:39:21Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-07-12 12:52:02.22099 +0000 UTC" + } + }, + "spec": { + "description": "Enables right panel for the plugins details page", + "stage": "experimental", + "codeowner": "@grafana/plugins-platform-backend", + "frontend": true + } + }, { "metadata": { "name": "pluginsFrontendSandbox", diff --git a/public/app/features/plugins/admin/__mocks__/remotePlugin.mock.ts b/public/app/features/plugins/admin/__mocks__/remotePlugin.mock.ts index 52b9d0a825c..d649c990042 100644 --- a/public/app/features/plugins/admin/__mocks__/remotePlugin.mock.ts +++ b/public/app/features/plugins/admin/__mocks__/remotePlugin.mock.ts @@ -4,6 +4,7 @@ import { RemotePlugin } from '../types'; // Copied from /api/gnet/plugins/alexanderzobnin-zabbix-app export default { + changelog: '', createdAt: '2016-04-06T20:23:41.000Z', description: 'Zabbix plugin for Grafana', downloads: 33645089, diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts index a86e32d6a1a..74415b39779 100644 --- a/public/app/features/plugins/admin/api.ts +++ b/public/app/features/plugins/admin/api.ts @@ -18,10 +18,11 @@ export async function getPluginDetails(id: string): Promise p.id === id); @@ -35,6 +36,7 @@ export async function getPluginDetails(id: string): Promise { } } +async function getLocalPluginChangelog(id: string): Promise { + try { + const markdown: string = await getBackendSrv().get(`${API_ROOT}/${id}/markdown/CHANGELOG`); + const markdownAsHtml = markdown ? renderMarkdown(markdown) : ''; + + return markdownAsHtml; + } catch (error) { + if (isFetchError(error)) { + error.isHandled = true; + } + return ''; + } +} + export async function getLocalPlugins(): Promise { const localPlugins: LocalPlugin[] = await getBackendSrv().get( `${API_ROOT}`, diff --git a/public/app/features/plugins/admin/components/Changelog.tsx b/public/app/features/plugins/admin/components/Changelog.tsx new file mode 100644 index 00000000000..97b9c78f132 --- /dev/null +++ b/public/app/features/plugins/admin/components/Changelog.tsx @@ -0,0 +1,44 @@ +import { css, cx } from '@emotion/css'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { useStyles2 } from '@grafana/ui'; + +interface Props { + sanitizedHTML: string; +} + +export const Changelog = ({ sanitizedHTML }: Props) => { + const styles = useStyles2(getStyles); + + return ( +
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + changelog: css({ + 'h1:first-of-type': { + display: 'none', + }, + 'h2:first-of-type': { + marginTop: 0, + }, + h2: { + marginTop: theme.spacing(2), + marginBottom: theme.spacing(1), + }, + li: { + marginLeft: theme.spacing(4), + }, + a: { + color: theme.colors.text.link, + '&:hover': { + color: theme.colors.text.link, + textDecoration: 'underline', + }, + }, + }), +}); diff --git a/public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx b/public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx index 9d10b8a6728..0cbf52ca151 100644 --- a/public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx +++ b/public/app/features/plugins/admin/components/InstallControls/InstallControlsWarning.tsx @@ -2,7 +2,7 @@ import { css } from '@emotion/css'; import { GrafanaTheme2, PluginType } from '@grafana/data'; import { config, featureEnabled } from '@grafana/runtime'; -import { Icon, LinkButton, Stack, useStyles2 } from '@grafana/ui'; +import { HorizontalGroup, LinkButton, useStyles2, Alert } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { AccessControlAction } from 'app/types'; @@ -24,67 +24,90 @@ export const InstallControlsWarning = ({ plugin, pluginStatus, latestCompatibleV const isCompatible = Boolean(latestCompatibleVersion); if (plugin.type === PluginType.renderer) { - return
Renderer plugins cannot be managed by the Plugin Catalog.
; + return ( + + ); } if (plugin.type === PluginType.secretsmanager) { - return
Secrets manager plugins cannot be managed by the Plugin Catalog.
; + return ( + + ); } if (plugin.isEnterprise && !featureEnabled('enterprise.plugins')) { return ( - - No valid Grafana Enterprise license detected. - - Learn more - - + + + No valid Grafana Enterprise license detected. + + Learn more + + + ); } if (plugin.isDev) { return ( -
This is a development build of the plugin and can't be uninstalled.
+ ); } if (!hasPermission && !isExternallyManaged) { - return
{statusToMessage(pluginStatus)}
; + return ; } if (!plugin.isPublished) { return ( -
- This plugin is not published to{' '} - - grafana.com/plugins - {' '} - and can't be managed via the catalog. -
+ +
+ This plugin is not published to{' '} + + grafana.com/plugins + {' '} + and can't be managed via the catalog. +
+
); } if (!isCompatible) { return ( -
- -  This plugin doesn't support your version of Grafana. -
+ ); } if (!isRemotePluginsAvailable) { return ( -
- The install controls have been disabled because the Grafana server cannot access grafana.com. -
+ ); } @@ -93,9 +116,9 @@ export const InstallControlsWarning = ({ plugin, pluginStatus, latestCompatibleV export const getStyles = (theme: GrafanaTheme2) => { return { - message: css` - color: ${theme.colors.text.secondary}; - `, + alert: css({ + marginTop: `${theme.spacing(2)}`, + }), }; }; diff --git a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx index 1c31ffb7b15..a905dbe30ee 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsBody.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsBody.tsx @@ -5,6 +5,7 @@ import { AppPlugin, GrafanaTheme2, PluginContextProvider, UrlQueryMap } from '@g import { config } from '@grafana/runtime'; import { CellProps, Column, InteractiveTable, Stack, useStyles2 } from '@grafana/ui'; +import { Changelog } from '../components/Changelog'; import { VersionList } from '../components/VersionList'; import { usePluginConfig } from '../hooks/usePluginConfig'; import { CatalogPlugin, Permission, PluginTabIds } from '../types'; @@ -60,6 +61,10 @@ export function PluginDetailsBody({ plugin, queryParams, pageId }: Props): JSX.E ); } + if (pageId === PluginTabIds.CHANGELOG && plugin?.details?.changelog) { + return ; + } + if (pageId === PluginTabIds.CONFIG && pluginConfig?.angularConfigCtrl) { return (
diff --git a/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx b/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx index 375bb27171a..5314def26a6 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsHeaderSignature.tsx @@ -1,13 +1,11 @@ import { css } from '@emotion/css'; import * as React from 'react'; -import { GrafanaTheme2, PluginSignatureStatus } from '@grafana/data'; +import { GrafanaTheme2 } from '@grafana/data'; import { PluginSignatureBadge, useStyles2 } from '@grafana/ui'; import { CatalogPlugin } from '../types'; -import { PluginSignatureDetailsBadge } from './PluginSignatureDetailsBadge'; - type Props = { plugin: CatalogPlugin; }; @@ -15,7 +13,6 @@ type Props = { // Designed to show plugin signature information in the header on the plugin's details page export function PluginDetailsHeaderSignature({ plugin }: Props): React.ReactElement { const styles = useStyles2(getStyles); - const isSignatureValid = plugin.signature === PluginSignatureStatus.valid; return (
@@ -25,12 +22,12 @@ export function PluginDetailsHeaderSignature({ plugin }: Props): React.ReactElem rel="noreferrer" className={styles.link} > - + - - {isSignatureValid && ( - - )}
); } diff --git a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx index c2dbbea44f3..47d03e5c5fd 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPage.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPage.tsx @@ -12,6 +12,7 @@ import { AngularDeprecationPluginNotice } from '../../angularDeprecation/Angular import { Loader } from '../components/Loader'; import { PluginDetailsBody } from '../components/PluginDetailsBody'; import { PluginDetailsDisabledError } from '../components/PluginDetailsDisabledError'; +import { PluginDetailsRightPanel } from '../components/PluginDetailsRightPanel'; import { PluginDetailsSignature } from '../components/PluginDetailsSignature'; import { usePluginDetailsTabs } from '../hooks/usePluginDetailsTabs'; import { usePluginPageExtensions } from '../hooks/usePluginPageExtensions'; @@ -72,26 +73,31 @@ export function PluginDetailsPage({ ); } + const conditionalProps = !config.featureToggles.pluginsDetailsRightPanel ? { info: info } : {}; + return ( - - - - {plugin.angularDetected && ( - - )} - - - - - - + + + + + {plugin.angularDetected && ( + + )} + + + + + + + {config.featureToggles.pluginsDetailsRightPanel && } + ); } diff --git a/public/app/features/plugins/admin/components/PluginDetailsRightPanel.tsx b/public/app/features/plugins/admin/components/PluginDetailsRightPanel.tsx new file mode 100644 index 00000000000..61303e61e33 --- /dev/null +++ b/public/app/features/plugins/admin/components/PluginDetailsRightPanel.tsx @@ -0,0 +1,55 @@ +import { PageInfoItem } from '@grafana/runtime/src/components/PluginPage'; +import { TextLink, Stack, Text } from '@grafana/ui'; +import { Trans } from 'app/core/internationalization'; +import { formatDate } from 'app/core/internationalization/dates'; + +import { CatalogPlugin } from '../types'; + +type Props = { + info: PageInfoItem[]; + plugin: CatalogPlugin; +}; + +export function PluginDetailsRightPanel(props: Props): React.ReactElement | null { + const { info, plugin } = props; + + return ( + + {info.map((infoItem, index) => { + return ( + + {infoItem.label + ':'} +
{infoItem.value}
+
+ ); + })} + + {plugin.updatedAt && ( +
+ + Last updated: + {' '} + {formatDate(new Date(plugin.updatedAt))} +
+ )} + + {plugin?.details?.links && plugin.details?.links?.length > 0 && ( + + {plugin.details.links.map((link, index) => ( +
+ + {link.name} + +
+ ))} +
+ )} + + {!plugin?.isCore && ( + + Report Abuse + + )} +
+ ); +} diff --git a/public/app/features/plugins/admin/components/PluginSubtitle.tsx b/public/app/features/plugins/admin/components/PluginSubtitle.tsx index ee1f42c18e8..53e604bd110 100644 --- a/public/app/features/plugins/admin/components/PluginSubtitle.tsx +++ b/public/app/features/plugins/admin/components/PluginSubtitle.tsx @@ -2,6 +2,7 @@ import { css } from '@emotion/css'; import { Fragment } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; +import { config } from '@grafana/runtime'; import { Alert, useStyles2 } from '@grafana/ui'; import { InstallControlsWarning } from '../components/InstallControls'; @@ -35,7 +36,7 @@ export const PluginSubtitle = ({ plugin }: Props) => { )} {plugin?.description &&
{plugin?.description}
} - {plugin?.details?.links && plugin.details.links.length > 0 && ( + {!config.featureToggles.pluginsDetailsRightPanel && !!plugin?.details?.links?.length && ( {plugin.details.links.map((link, index) => ( diff --git a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx index 5ff258b5916..3173e5df198 100644 --- a/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx +++ b/public/app/features/plugins/admin/hooks/usePluginDetailsTabs.tsx @@ -35,6 +35,15 @@ export const usePluginDetailsTabs = (plugin?: CatalogPlugin, pageId?: PluginTabI active: PluginTabIds.VERSIONS === currentPageId, }); } + if (isPublished && plugin?.details?.changelog) { + navModelChildren.push({ + text: PluginTabLabels.CHANGELOG, + id: PluginTabIds.CHANGELOG, + icon: 'rocket', + url: `${pathname}?page=${PluginTabIds.CHANGELOG}`, + active: PluginTabIds.CHANGELOG === currentPageId, + }); + } // Not extending the tabs with the config pages if the plugin is not installed if (!pluginConfig) { diff --git a/public/app/features/plugins/admin/hooks/usePluginInfo.tsx b/public/app/features/plugins/admin/hooks/usePluginInfo.tsx index e50b393e022..54ccebbb21a 100644 --- a/public/app/features/plugins/admin/hooks/usePluginInfo.tsx +++ b/public/app/features/plugins/admin/hooks/usePluginInfo.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/css'; import { GrafanaTheme2, PluginSignatureType } from '@grafana/data'; +import { t } from 'app/core/internationalization'; import { PageInfoItem } from '../../../../core/components/Page/types'; import { PluginDisabledBadge } from '../components/Badges'; @@ -27,12 +28,12 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => { if (Boolean(version)) { if (plugin.isManaged) { info.push({ - label: 'Version', + label: t('plugins.details.labels.version', 'Version'), value: 'Managed by Grafana', }); } else { info.push({ - label: 'Version', + label: t('plugins.details.labels.version', 'Version'), value: version, }); } @@ -40,7 +41,7 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => { if (Boolean(plugin.orgName)) { info.push({ - label: 'From', + label: t('plugins.details.labels.from', 'From'), value: plugin.orgName, }); } @@ -51,7 +52,7 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => { plugin.signatureType === PluginSignatureType.commercial; if (showDownloads && Boolean(plugin.downloads > 0)) { info.push({ - label: 'Downloads', + label: t('plugins.details.labels.downloads', 'Downloads'), value: new Intl.NumberFormat().format(plugin.downloads), }); } @@ -65,20 +66,20 @@ export const usePluginInfo = (plugin?: CatalogPlugin): PageInfoItem[] => { if (!hasNoDependencyInfo) { info.push({ - label: 'Dependencies', + label: t('plugins.details.labels.dependencies', 'Dependencies'), value: , }); } if (plugin.isDisabled) { info.push({ - label: 'Status', + label: t('plugins.details.labels.status', 'Status'), value: , }); } info.push({ - label: 'Signature', + label: t('plugins.details.labels.signature', 'Signature'), value: , }); diff --git a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx index e13a855336b..931d20cb16a 100644 --- a/public/app/features/plugins/admin/pages/PluginDetails.test.tsx +++ b/public/app/features/plugins/admin/pages/PluginDetails.test.tsx @@ -215,7 +215,7 @@ describe('Plugin details page', () => { it('should display a "Signed" badge if the plugin signature is verified', async () => { const { queryByText } = renderPluginDetails({ id, signature: PluginSignatureStatus.valid }); - expect(await queryByText('Signed')).toBeInTheDocument(); + expect(await queryByText('community')).toBeInTheDocument(); }); it('should display a "Missing signature" badge if the plugin signature is missing', async () => { @@ -880,4 +880,42 @@ describe('Plugin details page', () => { expect(queryByText('Add new data source')).toBeNull(); }); }); + + describe('Display plugin details right panel', () => { + beforeAll(() => { + mockUserPermissions({ + isAdmin: true, + isDataSourceEditor: false, + isOrgAdmin: true, + }); + config.featureToggles.pluginsDetailsRightPanel = true; + }); + + afterAll(() => { + config.featureToggles.pluginsDetailsRightPanel = false; + }); + + it('should display Last updated and report abuse information', async () => { + const id = 'right-panel-test-plugin'; + const updatedAt = '2023-10-26T16:54:55.000Z'; + const { queryByText } = renderPluginDetails({ id, updatedAt }); + expect(queryByText('Last updated:')).toBeVisible(); + expect(queryByText('10/26/2023')).toBeVisible(); + expect(queryByText('Report Abuse')).toBeVisible(); + }); + + it('should not display Last updated if there is no updated At data', async () => { + const id = 'right-panel-test-plugin'; + const updatedAt = undefined; + const { queryByText } = renderPluginDetails({ id, updatedAt }); + expect(queryByText('Last updated:')).toBeNull(); + }); + + it('should not display Report Abuse if the plugin is Core', async () => { + const id = 'right-panel-test-plugin'; + const isCore = true; + const { queryByText } = renderPluginDetails({ id, isCore }); + expect(queryByText('Report Abuse')).toBeNull(); + }); + }); }); diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index aa6b41c7737..0991c0921bb 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -80,6 +80,7 @@ export interface CatalogPluginDetails { pluginDependencies?: PluginDependencies['plugins']; statusContext?: string; iam?: IdentityAccessManagement; + changelog?: string; } export interface CatalogPluginInfo { @@ -91,6 +92,7 @@ export interface CatalogPluginInfo { } export type RemotePlugin = { + changelog: string; createdAt: string; description: string; downloads: number; @@ -252,6 +254,7 @@ export enum PluginTabLabels { DASHBOARDS = 'Dashboards', USAGE = 'Usage', IAM = 'IAM', + CHANGELOG = 'Changelog', } export enum PluginTabIds { @@ -261,6 +264,7 @@ export enum PluginTabIds { DASHBOARDS = 'dashboards', USAGE = 'usage', IAM = 'iam', + CHANGELOG = 'changelog', } export enum RequestStatus { diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index f5a96871f45..2cefb4996dd 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1656,6 +1656,18 @@ } }, "plugins": { + "details": { + "labels": { + "dependencies": "Dependencies", + "downloads": "Downloads", + "from": "From", + "reportAbuse": "Report Abuse", + "signature": "Signature", + "status": "Status", + "updatedAt": "Last updated: ", + "version": "Version" + } + }, "empty-state": { "message": "No plugins found" } diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 729d80a2982..3ca86e26862 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -1656,6 +1656,18 @@ } }, "plugins": { + "details": { + "labels": { + "dependencies": "Đępęʼnđęʼnčįęş", + "downloads": "Đőŵʼnľőäđş", + "from": "Fřőm", + "reportAbuse": "Ŗępőřŧ Åþūşę", + "signature": "Ŝįģʼnäŧūřę", + "status": "Ŝŧäŧūş", + "updatedAt": "Ŀäşŧ ūpđäŧęđ: ", + "version": "Vęřşįőʼn" + } + }, "empty-state": { "message": "Ńő pľūģįʼnş ƒőūʼnđ" } From b2eeb0dd6ed5957d4eb76df3aafcd8e4dc46902e Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 13 Aug 2024 12:26:26 +0200 Subject: [PATCH 039/229] Alerting: update rule versions on folder move (#88376) * Alerting: update rule versions on folder move (#88361) * Add tracing to folder.Move and folder.Update --- pkg/api/dashboard_test.go | 2 +- pkg/api/folder_bench_test.go | 2 +- pkg/events/events.go | 9 ++- .../annotationsimpl/annotations_test.go | 2 +- .../database/database_folder_test.go | 2 +- .../dashboards/database/database_test.go | 4 +- pkg/services/folder/folderimpl/folder.go | 59 ++++++++++++++++--- pkg/services/folder/folderimpl/folder_test.go | 12 +++- .../libraryelements/libraryelements_test.go | 4 +- .../librarypanels/librarypanels_test.go | 4 +- .../ngalert/api/api_provisioning_test.go | 2 +- pkg/services/ngalert/api/persist.go | 4 +- pkg/services/ngalert/ngalert.go | 11 ++-- pkg/services/ngalert/ngalert_test.go | 44 +++++++++----- .../ngalert/provisioning/alert_rules_test.go | 2 +- pkg/services/ngalert/store/alert_rule.go | 18 ++++-- pkg/services/ngalert/store/alert_rule_test.go | 52 ++++++++++++++++ pkg/services/ngalert/tests/fakes/rules.go | 13 ++-- pkg/services/ngalert/testutil/testutil.go | 3 +- .../sqlstore/permissions/dashboard_test.go | 2 +- .../permissions/dashboards_bench_test.go | 2 +- 21 files changed, 196 insertions(+), 57 deletions(-) diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 09855077aac..617d836f22a 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -834,7 +834,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr dashboardPermissions := accesscontrolmock.NewMockedPermissionsService() folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), - dashboardStore, folderStore, db.InitTestDB(t), features, supportbundlestest.NewFakeBundleService(), nil) + dashboardStore, folderStore, db.InitTestDB(t), features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) if dashboardService == nil { dashboardService, err = service.ProvideDashboardServiceImpl( diff --git a/pkg/api/folder_bench_test.go b/pkg/api/folder_bench_test.go index 02f729ff2b2..dc4a844254b 100644 --- a/pkg/api/folder_bench_test.go +++ b/pkg/api/folder_bench_test.go @@ -458,7 +458,7 @@ func setupServer(b testing.TB, sc benchScenario, features featuremgmt.FeatureTog folderStore := folderimpl.ProvideDashboardFolderStore(sc.db.DB()) ac := acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()) - folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, sc.db.DB(), features, supportbundlestest.NewFakeBundleService(), nil) + folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderStore, sc.db.DB(), features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) cfg := setting.NewCfg() actionSets := resourcepermissions.NewActionSetService(features) diff --git a/pkg/events/events.go b/pkg/events/events.go index 9ad8d51a6f6..38b6dce4090 100644 --- a/pkg/events/events.go +++ b/pkg/events/events.go @@ -71,10 +71,13 @@ type DataSourceCreated struct { OrgID int64 `json:"org_id"` } -type FolderTitleUpdated struct { +// FolderFullPathUpdated is emitted when the full path of the folder(s) is updated. +// For example, when the folder is renamed or moved to another folder. +// It does not contain the full path of the folders because calculating +// it requires more resources and not needed in the event at the moment. +type FolderFullPathUpdated struct { Timestamp time.Time `json:"timestamp"` - Title string `json:"name"` ID int64 `json:"id"` - UID string `json:"uid"` + UIDs []string `json:"uids"` OrgID int64 `json:"org_id"` } diff --git a/pkg/services/annotations/annotationsimpl/annotations_test.go b/pkg/services/annotations/annotationsimpl/annotations_test.go index 8e8a7e92d06..99f797de0cd 100644 --- a/pkg/services/annotations/annotationsimpl/annotations_test.go +++ b/pkg/services/annotations/annotationsimpl/annotations_test.go @@ -225,7 +225,7 @@ func TestIntegrationAnnotationListingWithInheritedRBAC(t *testing.T) { }) ac := acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()) - folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderimpl.ProvideDashboardFolderStore(sql), sql, features, supportbundlestest.NewFakeBundleService(), nil) + folderSvc := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderimpl.ProvideDashboardFolderStore(sql), sql, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) cfg.AnnotationMaximumTagsLength = 60 diff --git a/pkg/services/dashboards/database/database_folder_test.go b/pkg/services/dashboards/database/database_folder_test.go index ffbf6413c79..6f52c415e55 100644 --- a/pkg/services/dashboards/database/database_folder_test.go +++ b/pkg/services/dashboards/database/database_folder_test.go @@ -303,7 +303,7 @@ func TestIntegrationDashboardInheritedFolderRBAC(t *testing.T) { guardian.New = origNewGuardian }) - folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracer), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore.DB()), sqlStore.DB(), features, supportbundlestest.NewFakeBundleService(), nil) + folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracer), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(sqlStore.DB()), sqlStore.DB(), features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) parentUID := "" for i := 0; ; i++ { diff --git a/pkg/services/dashboards/database/database_test.go b/pkg/services/dashboards/database/database_test.go index b36337db2d8..0e77bfee2d5 100644 --- a/pkg/services/dashboards/database/database_test.go +++ b/pkg/services/dashboards/database/database_test.go @@ -830,7 +830,7 @@ func TestIntegrationFindDashboardsByTitle(t *testing.T) { ac := acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) - folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) + folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) user := &user.SignedInUser{ OrgID: 1, @@ -948,7 +948,7 @@ func TestIntegrationFindDashboardsByFolder(t *testing.T) { ac := acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) - folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) + folderServiceWithFlagOn := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) user := &user.SignedInUser{ OrgID: 1, diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index c3b36588454..48cc32b7b76 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -13,6 +13,8 @@ import ( "github.com/grafana/dskit/concurrency" "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel/attribute" + "go.opentelemetry.io/otel/trace" "golang.org/x/exp/slices" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -20,6 +22,7 @@ import ( "github.com/grafana/grafana/pkg/events" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess" @@ -44,12 +47,14 @@ type Service struct { dashboardFolderStore folder.FolderStore features featuremgmt.FeatureToggles accessControl accesscontrol.AccessControl - // bus is currently used to publish event in case of title change + // bus is currently used to publish event in case of folder full path change. + // For example when a folder is moved to another folder or when a folder is renamed. bus bus.Bus mutex sync.RWMutex registry map[string]folder.RegistryService metrics *foldersMetrics + tracer tracing.Tracer } func ProvideService( @@ -61,6 +66,7 @@ func ProvideService( features featuremgmt.FeatureToggles, supportBundles supportbundles.Service, r prometheus.Registerer, + tracer tracing.Tracer, ) folder.Service { store := ProvideStore(db) srv := &Service{ @@ -74,6 +80,7 @@ func ProvideService( db: db, registry: make(map[string]folder.RegistryService), metrics: newFoldersMetrics(r), + tracer: tracer, } srv.DBMigration(db) @@ -655,6 +662,9 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) ( } func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) (*folder.Folder, error) { + ctx, span := s.tracer.Start(ctx, "folder.Update") + defer span.End() + if cmd.SignedInUser == nil { return nil, folder.ErrBadRequest.Errorf("missing signed in user") } @@ -679,14 +689,8 @@ func (s *Service) Update(ctx context.Context, cmd *folder.UpdateFolderCommand) ( if cmd.NewTitle != nil { metrics.MFolderIDsServiceCount.WithLabelValues(metrics.Folder).Inc() - if err := s.bus.Publish(ctx, &events.FolderTitleUpdated{ - Timestamp: foldr.Updated, - Title: foldr.Title, - ID: dashFolder.ID, // nolint:staticcheck - UID: dashFolder.UID, - OrgID: cmd.OrgID, - }); err != nil { - s.log.ErrorContext(ctx, "failed to publish FolderTitleUpdated event", "folder", foldr.Title, "user", cmd.SignedInUser.GetID(), "error", err) + + if err := s.publishFolderFullPathUpdatedEvent(ctx, foldr.Updated, cmd.OrgID, cmd.UID); err != nil { return err } } @@ -873,6 +877,9 @@ func (s *Service) legacyDelete(ctx context.Context, cmd *folder.DeleteFolderComm } func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*folder.Folder, error) { + ctx, span := s.tracer.Start(ctx, "folder.Move") + defer span.End() + if cmd.SignedInUser == nil { return nil, folder.ErrBadRequest.Errorf("missing signed in user") } @@ -947,6 +954,10 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol return folder.ErrInternal.Errorf("failed to move legacy folder: %w", err) } + if err := s.publishFolderFullPathUpdatedEvent(ctx, f.Updated, cmd.OrgID, cmd.UID); err != nil { + return err + } + return nil }); err != nil { return nil, err @@ -954,6 +965,36 @@ func (s *Service) Move(ctx context.Context, cmd *folder.MoveFolderCommand) (*fol return f, nil } +func (s *Service) publishFolderFullPathUpdatedEvent(ctx context.Context, timestamp time.Time, orgID int64, folderUID string) error { + ctx, span := s.tracer.Start(ctx, "folder.publishFolderFullPathUpdatedEvent") + defer span.End() + + descFolders, err := s.store.GetDescendants(ctx, orgID, folderUID) + if err != nil { + s.log.ErrorContext(ctx, "Failed to get descendants of the folder", "folderUID", folderUID, "orgID", orgID, "error", err) + return err + } + uids := make([]string, 0, len(descFolders)+1) + uids = append(uids, folderUID) + for _, f := range descFolders { + uids = append(uids, f.UID) + } + span.AddEvent("found folder descendants", trace.WithAttributes( + attribute.Int64("folders", int64(len(uids))), + )) + + if err := s.bus.Publish(ctx, &events.FolderFullPathUpdated{ + Timestamp: timestamp, + UIDs: uids, + OrgID: orgID, + }); err != nil { + s.log.ErrorContext(ctx, "Failed to publish FolderFullPathUpdated event", "folderUID", folderUID, "orgID", orgID, "descendantsUIDs", uids, "error", err) + return err + } + + return nil +} + func (s *Service) canMove(ctx context.Context, cmd *folder.MoveFolderCommand) (bool, error) { // Check that the user is allowed to move the folder to the destination folder var evaluator accesscontrol.Evaluator diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index ee18314c129..b55017af13f 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -63,7 +63,7 @@ func TestIntegrationProvideFolderService(t *testing.T) { t.Run("should register scope resolvers", func(t *testing.T) { ac := acmock.New() db := db.InitTestDB(t) - ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), nil, nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil) + ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), nil, nil, db, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) require.Len(t, ac.Calls.RegisterAttributeScopeResolver, 3) }) @@ -100,6 +100,7 @@ func TestIntegrationFolderService(t *testing.T) { accessControl: acimpl.ProvideAccessControl(features, zanzana.NewNoopClient()), metrics: newFoldersMetrics(nil), registry: make(map[string]folder.RegistryService), + tracer: tracing.InitializeTracerForTest(), } require.NoError(t, service.RegisterService(alertingStore)) @@ -440,6 +441,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { accessControl: ac, registry: make(map[string]folder.RegistryService), metrics: newFoldersMetrics(nil), + tracer: tracing.InitializeTracerForTest(), } signedInUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{ @@ -553,6 +555,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { db: db, registry: make(map[string]folder.RegistryService), metrics: newFoldersMetrics(nil), + tracer: tracing.InitializeTracerForTest(), } origNewGuardian := guardian.New @@ -630,6 +633,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { db: db, registry: make(map[string]folder.RegistryService), metrics: newFoldersMetrics(nil), + tracer: tracing.InitializeTracerForTest(), } testCases := []struct { @@ -805,6 +809,7 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) { features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders), accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), metrics: newFoldersMetrics(nil), + tracer: tracing.InitializeTracerForTest(), } t.Run("create folder", func(t *testing.T) { nestedFolderStore.ExpectedFolder = &folder.Folder{ParentUID: util.GenerateShortUID()} @@ -841,6 +846,7 @@ func TestFolderServiceDualWrite(t *testing.T) { features: featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders), accessControl: acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()), metrics: newFoldersMetrics(nil), + tracer: tracing.InitializeTracerForTest(), bus: bus.ProvideBus(tracing.InitializeTracerForTest()), } @@ -1475,6 +1481,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { accessControl: ac, registry: make(map[string]folder.RegistryService), metrics: newFoldersMetrics(nil), + tracer: tracing.InitializeTracerForTest(), } dashboardPermissions := acmock.NewMockedPermissionsService() @@ -1977,6 +1984,7 @@ func TestFolderServiceGetFolders(t *testing.T) { accessControl: ac, registry: make(map[string]folder.RegistryService), metrics: newFoldersMetrics(nil), + tracer: tracing.InitializeTracerForTest(), } signedInAdminUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{ @@ -2063,6 +2071,7 @@ func TestGetChildrenFilterByPermission(t *testing.T) { accessControl: ac, registry: make(map[string]folder.RegistryService), metrics: newFoldersMetrics(nil), + tracer: tracing.InitializeTracerForTest(), } origGuardian := guardian.New @@ -2523,6 +2532,7 @@ func setup(t *testing.T, dashStore dashboards.Store, dashboardFolderStore folder accessControl: ac, db: db, metrics: newFoldersMetrics(nil), + tracer: tracing.InitializeTracerForTest(), } } diff --git a/pkg/services/libraryelements/libraryelements_test.go b/pkg/services/libraryelements/libraryelements_test.go index 3b3b3d77fd5..9937ceb6aae 100644 --- a/pkg/services/libraryelements/libraryelements_test.go +++ b/pkg/services/libraryelements/libraryelements_test.go @@ -329,7 +329,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder require.NoError(t, err) folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore) - s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) + s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) t.Logf("Creating folder with title and UID %q", title) ctx := identity.WithRequester(context.Background(), &sc.user) folder, err := s.Create(ctx, &folder.CreateFolderCommand{ @@ -463,7 +463,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo Cfg: cfg, features: featuremgmt.WithFeatures(), SQLStore: sqlStore, - folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracer), dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil), + folderService: folderimpl.ProvideService(ac, bus.ProvideBus(tracer), dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()), } // deliberate difference between signed in user and user in db to make it crystal clear diff --git a/pkg/services/librarypanels/librarypanels_test.go b/pkg/services/librarypanels/librarypanels_test.go index 3ff533995e6..a5389988491 100644 --- a/pkg/services/librarypanels/librarypanels_test.go +++ b/pkg/services/librarypanels/librarypanels_test.go @@ -753,7 +753,7 @@ func createFolder(t *testing.T, sc scenarioContext, title string) *folder.Folder dashboardStore, err := database.ProvideDashboardStore(sc.replStore, cfg, features, tagimpl.ProvideService(sc.sqlStore), quotaService) require.NoError(t, err) folderStore := folderimpl.ProvideDashboardFolderStore(sc.sqlStore) - s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) + s := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sc.sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) t.Logf("Creating folder with title and UID %q", title) ctx := identity.WithRequester(context.Background(), sc.user) @@ -836,7 +836,7 @@ func testScenario(t *testing.T, desc string, fn func(t *testing.T, sc scenarioCo dashboardStore, err := database.ProvideDashboardStore(replStore, cfg, featuremgmt.WithFeatures(), tagimpl.ProvideService(sqlStore), quotaService) require.NoError(t, err) features := featuremgmt.WithFeatures() - folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) + folderService := folderimpl.ProvideService(ac, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) elementService := libraryelements.ProvideService(cfg, sqlStore, routing.NewRouteRegister(), folderService, featuremgmt.WithFeatures(), ac) service := LibraryPanelService{ diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index 5bc102cd90a..de7b337353d 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -1819,7 +1819,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment { require.NoError(t, err) folderStore := folderimpl.ProvideDashboardFolderStore(sqlStore) - folderService := folderimpl.ProvideService(actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil) + folderService := folderimpl.ProvideService(actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardStore, folderStore, sqlStore, featuremgmt.WithFeatures(), supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) store := store.DBstore{ Logger: log, SQLStore: sqlStore, diff --git a/pkg/services/ngalert/api/persist.go b/pkg/services/ngalert/api/persist.go index 28c984dd22b..faecfa2ccc4 100644 --- a/pkg/services/ngalert/api/persist.go +++ b/pkg/services/ngalert/api/persist.go @@ -26,8 +26,8 @@ type RuleStore interface { UpdateAlertRules(ctx context.Context, rule []ngmodels.UpdateRule) error DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUID ...string) error - // IncreaseVersionForAllRulesInNamespace Increases version for all rules that have specified namespace. Returns all rules that belong to the namespace - IncreaseVersionForAllRulesInNamespace(ctx context.Context, orgID int64, namespaceUID string) ([]ngmodels.AlertRuleKeyWithVersion, error) + // IncreaseVersionForAllRulesInNamespaces Increases version for all rules that have specified namespace uids + IncreaseVersionForAllRulesInNamespaces(ctx context.Context, orgID int64, namespaceUIDs []string) ([]ngmodels.AlertRuleKeyWithVersion, error) accesscontrol.RuleUIDToNamespaceStore } diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index b85aa73a871..e315e302fcb 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -487,15 +487,16 @@ func (ng *AlertNG) init() error { } func subscribeToFolderChanges(logger log.Logger, bus bus.Bus, dbStore api.RuleStore) { - // if folder title is changed, we update all alert rules in that folder to make sure that all peers (in HA mode) will update folder title and + // if full path to the folder is changed, we update all alert rules in that folder to make sure that all peers (in HA mode) will update folder title and // clean up the current state - bus.AddEventListener(func(ctx context.Context, evt *events.FolderTitleUpdated) error { - logger.Info("Got folder title updated event. updating rules in the folder", "folderUID", evt.UID) - _, err := dbStore.IncreaseVersionForAllRulesInNamespace(ctx, evt.OrgID, evt.UID) + bus.AddEventListener(func(ctx context.Context, evt *events.FolderFullPathUpdated) error { + logger.Info("Got folder full path updated event. updating rules in the folders", "folderUIDs", evt.UIDs) + updatedKeys, err := dbStore.IncreaseVersionForAllRulesInNamespaces(ctx, evt.OrgID, evt.UIDs) if err != nil { - logger.Error("Failed to update alert rules in the folder after its title was changed", "error", err, "folderUID", evt.UID, "folder", evt.Title) + logger.Error("Failed to update alert rules in the folders after their full paths were changed", "error", err, "folderUIDs", evt.UIDs, "orgID", evt.OrgID) return err } + logger.Info("Updated version for alert rules", "keys", updatedKeys) return nil }) } diff --git a/pkg/services/ngalert/ngalert_test.go b/pkg/services/ngalert/ngalert_test.go index b64dbb5092d..82542eeba71 100644 --- a/pkg/services/ngalert/ngalert_test.go +++ b/pkg/services/ngalert/ngalert_test.go @@ -9,6 +9,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/bus" @@ -25,37 +26,52 @@ import ( ) func Test_subscribeToFolderChanges(t *testing.T) { + getRecordedCommand := func(ruleStore *fakes.RuleStore) []fakes.GenericRecordedQuery { + results := ruleStore.GetRecordedCommands(func(cmd any) (any, bool) { + c, ok := cmd.(fakes.GenericRecordedQuery) + if !ok || c.Name != "IncreaseVersionForAllRulesInNamespaces" { + return nil, false + } + return c, ok + }) + var result []fakes.GenericRecordedQuery + for _, cmd := range results { + result = append(result, cmd.(fakes.GenericRecordedQuery)) + } + return result + } + orgID := rand.Int63() - folder := &folder.Folder{ + folder1 := &folder.Folder{ + UID: util.GenerateShortUID(), + Title: "Folder" + util.GenerateShortUID(), + } + folder2 := &folder.Folder{ UID: util.GenerateShortUID(), Title: "Folder" + util.GenerateShortUID(), } gen := models.RuleGen - rules := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder)).GenerateManyRef(5) + rules := gen.With(gen.WithOrgID(orgID), gen.WithNamespace(folder1)).GenerateManyRef(5) bus := bus.ProvideBus(tracing.InitializeTracerForTest()) db := fakes.NewRuleStore(t) - db.Folders[orgID] = append(db.Folders[orgID], folder) + db.Folders[orgID] = append(db.Folders[orgID], folder1) db.PutRule(context.Background(), rules...) subscribeToFolderChanges(log.New("test"), bus, db) - err := bus.Publish(context.Background(), &events.FolderTitleUpdated{ + err := bus.Publish(context.Background(), &events.FolderFullPathUpdated{ Timestamp: time.Now(), - Title: "Folder" + util.GenerateShortUID(), - UID: folder.UID, + UIDs: []string{folder1.UID, folder2.UID}, OrgID: orgID, }) require.NoError(t, err) - require.Eventuallyf(t, func() bool { - return len(db.GetRecordedCommands(func(cmd any) (any, bool) { - c, ok := cmd.(fakes.GenericRecordedQuery) - if !ok || c.Name != "IncreaseVersionForAllRulesInNamespace" { - return nil, false - } - return c, true - })) > 0 + require.EventuallyWithT(t, func(c *assert.CollectT) { + recordedCommands := getRecordedCommand(db) + require.Len(c, recordedCommands, 1) + require.Equal(c, recordedCommands[0].Params[0].(int64), orgID) + require.ElementsMatch(c, recordedCommands[0].Params[1].([]string), []string{folder1.UID, folder2.UID}) }, time.Second, 10*time.Millisecond, "expected to call db store method but nothing was called") } diff --git a/pkg/services/ngalert/provisioning/alert_rules_test.go b/pkg/services/ngalert/provisioning/alert_rules_test.go index 88e95d24c37..5728b976230 100644 --- a/pkg/services/ngalert/provisioning/alert_rules_test.go +++ b/pkg/services/ngalert/provisioning/alert_rules_test.go @@ -1479,7 +1479,7 @@ func TestProvisiongWithFullpath(t *testing.T) { _, dashboardStore := testutil.SetupDashboardService(t, sqlStore, folderStore, cfg) ac := acmock.New() features := featuremgmt.WithFeatures(featuremgmt.FlagNestedFolders) - folderService := folderimpl.ProvideService(ac, inProcBus, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil) + folderService := folderimpl.ProvideService(ac, inProcBus, dashboardStore, folderStore, sqlStore, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) ruleService := createAlertRuleService(t, folderService) var orgID int64 = 1 diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 6198f2f5e77..547b9174886 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -75,16 +75,26 @@ func (st DBstore) DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUI }) } -// IncreaseVersionForAllRulesInNamespace Increases version for all rules that have specified namespace. Returns all rules that belong to the namespace -func (st DBstore) IncreaseVersionForAllRulesInNamespace(ctx context.Context, orgID int64, namespaceUID string) ([]ngmodels.AlertRuleKeyWithVersion, error) { +// IncreaseVersionForAllRulesInNamespaces Increases version for all rules that have specified namespace. Returns all rules that belong to the namespaces +func (st DBstore) IncreaseVersionForAllRulesInNamespaces(ctx context.Context, orgID int64, namespaceUIDs []string) ([]ngmodels.AlertRuleKeyWithVersion, error) { var keys []ngmodels.AlertRuleKeyWithVersion err := st.SQLStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error { now := TimeNow() - _, err := sess.Exec("UPDATE alert_rule SET version = version + 1, updated = ? WHERE namespace_uid = ? AND org_id = ?", now, namespaceUID, orgID) + namespaceUIDsArgs, in := getINSubQueryArgs(namespaceUIDs) + sql := fmt.Sprintf( + "UPDATE alert_rule SET version = version + 1, updated = ? WHERE org_id = ? AND namespace_uid IN (%s)", + strings.Join(in, ","), + ) + args := make([]interface{}, 0, 3+len(namespaceUIDsArgs)) + args = append(args, sql, now, orgID) + args = append(args, namespaceUIDsArgs...) + + _, err := sess.Exec(args...) if err != nil { return err } - return sess.Table(ngmodels.AlertRule{}).Where("namespace_uid = ? AND org_id = ?", namespaceUID, orgID).Find(&keys) + + return sess.Table(ngmodels.AlertRule{}).Where("org_id = ?", orgID).In("namespace_uid", namespaceUIDs).Find(&keys) }) return keys, err } diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index a6ac6aaf0b7..0733a264221 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -1184,6 +1184,58 @@ func TestIntegrationRuleGroupsCaseSensitive(t *testing.T) { }) } +func TestIncreaseVersionForAllRulesInNamespaces(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + cfg := setting.NewCfg() + cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{BaseInterval: time.Duration(rand.Int63n(100)+1) * time.Second} + sqlStore := db.InitTestReplDB(t) + store := &DBstore{ + SQLStore: sqlStore, + Cfg: cfg.UnifiedAlerting, + FolderService: setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()), + Logger: &logtest.Fake{}, + } + orgID := int64(1) + gen := models.RuleGen + gen = gen.With(gen.WithIntervalMatching(store.Cfg.BaseInterval)).With(gen.WithOrgID(orgID)) + + alertRules := []*models.AlertRule{} + for i := 0; i < 5; i++ { + alertRules = append(alertRules, createRule(t, store, gen)) + } + alertRuleNamespaceUIDs := make([]string, 0, len(alertRules)) + for _, rule := range alertRules { + alertRuleNamespaceUIDs = append(alertRuleNamespaceUIDs, rule.NamespaceUID) + } + alertRuleInAnotherNamespace := createRule(t, store, gen) + + requireAlertRuleVersion := func(t *testing.T, ruleID int64, orgID int64, expectedVersion int64) { + t.Helper() + dbrule := &models.AlertRule{} + err := sqlStore.WithDbSession(context.Background(), func(sess *db.Session) error { + exist, err := sess.Table(models.AlertRule{}).ID(ruleID).Get(dbrule) + require.Truef(t, exist, fmt.Sprintf("rule with ID %d does not exist", ruleID)) + return err + }) + require.NoError(t, err) + require.Equal(t, expectedVersion, dbrule.Version) + } + + t.Run("should increase version for all rules", func(t *testing.T) { + _, err := store.IncreaseVersionForAllRulesInNamespaces(context.Background(), orgID, alertRuleNamespaceUIDs) + require.NoError(t, err) + + for _, rule := range alertRules { + requireAlertRuleVersion(t, rule.ID, orgID, rule.Version+1) + } + + // this rule's version should not be changed + requireAlertRuleVersion(t, alertRuleInAnotherNamespace.ID, orgID, alertRuleInAnotherNamespace.Version) + }) +} + // createAlertRule creates an alert rule in the database and returns it. // If a generator is not specified, uniqueness of primary key is not guaranteed. func createRule(t *testing.T, store *DBstore, generator *models.AlertRuleGenerator) *models.AlertRule { diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index 61eb036a1d4..014de832bfe 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -315,19 +315,24 @@ func (f *RuleStore) UpdateRuleGroup(ctx context.Context, orgID int64, namespaceU return nil } -func (f *RuleStore) IncreaseVersionForAllRulesInNamespace(_ context.Context, orgID int64, namespaceUID string) ([]models.AlertRuleKeyWithVersion, error) { +func (f *RuleStore) IncreaseVersionForAllRulesInNamespaces(_ context.Context, orgID int64, namespaceUIDs []string) ([]models.AlertRuleKeyWithVersion, error) { f.mtx.Lock() defer f.mtx.Unlock() f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{ - Name: "IncreaseVersionForAllRulesInNamespace", - Params: []any{orgID, namespaceUID}, + Name: "IncreaseVersionForAllRulesInNamespaces", + Params: []any{orgID, namespaceUIDs}, }) var result []models.AlertRuleKeyWithVersion + namespaceUIDsMap := make(map[string]struct{}, len(namespaceUIDs)) + for _, namespaceUID := range namespaceUIDs { + namespaceUIDsMap[namespaceUID] = struct{}{} + } + for _, rule := range f.Rules[orgID] { - if rule.NamespaceUID == namespaceUID && rule.OrgID == orgID { + if _, ok := namespaceUIDsMap[rule.NamespaceUID]; ok && rule.OrgID == orgID { rule.Version++ rule.Updated = time.Now() result = append(result, models.AlertRuleKeyWithVersion{ diff --git a/pkg/services/ngalert/testutil/testutil.go b/pkg/services/ngalert/testutil/testutil.go index 1f8e032617c..a1986de8734 100644 --- a/pkg/services/ngalert/testutil/testutil.go +++ b/pkg/services/ngalert/testutil/testutil.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/db" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/accesscontrol" acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock" "github.com/grafana/grafana/pkg/services/dashboards" @@ -26,7 +27,7 @@ import ( func SetupFolderService(tb testing.TB, cfg *setting.Cfg, db db.DB, dashboardStore dashboards.Store, folderStore *folderimpl.DashboardFolderStoreImpl, bus *bus.InProcBus, features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl) folder.Service { tb.Helper() - return folderimpl.ProvideService(ac, bus, dashboardStore, folderStore, db, features, supportbundlestest.NewFakeBundleService(), nil) + return folderimpl.ProvideService(ac, bus, dashboardStore, folderStore, db, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) } func SetupDashboardService(tb testing.TB, sqlStore db.ReplDB, fs *folderimpl.DashboardFolderStoreImpl, cfg *setting.Cfg) (*dashboardservice.DashboardServiceImpl, dashboards.Store) { diff --git a/pkg/services/sqlstore/permissions/dashboard_test.go b/pkg/services/sqlstore/permissions/dashboard_test.go index a9c29d04e64..1acf17fce41 100644 --- a/pkg/services/sqlstore/permissions/dashboard_test.go +++ b/pkg/services/sqlstore/permissions/dashboard_test.go @@ -822,7 +822,7 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol dashStore, err := database.ProvideDashboardStore(db, cfg, features, tagimpl.ProvideService(db), quotatest.New(false, nil)) require.NoError(t, err) - folderSvc := folderimpl.ProvideService(actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderimpl.ProvideDashboardFolderStore(db), db, features, supportbundlestest.NewFakeBundleService(), nil) + folderSvc := folderimpl.ProvideService(actest.FakeAccessControl{ExpectedEvaluate: true}, bus.ProvideBus(tracing.InitializeTracerForTest()), dashStore, folderimpl.ProvideDashboardFolderStore(db), db, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) // create parent folder parent, err := folderSvc.Create(context.Background(), &folder.CreateFolderCommand{ diff --git a/pkg/services/sqlstore/permissions/dashboards_bench_test.go b/pkg/services/sqlstore/permissions/dashboards_bench_test.go index 362905c447e..dfb2fe43b81 100644 --- a/pkg/services/sqlstore/permissions/dashboards_bench_test.go +++ b/pkg/services/sqlstore/permissions/dashboards_bench_test.go @@ -81,7 +81,7 @@ func setupBenchMark(b *testing.B, usr user.SignedInUser, features featuremgmt.Fe dashboardWriteStore, err := database.ProvideDashboardStore(store, cfg, features, tagimpl.ProvideService(store), quotaService) require.NoError(b, err) - folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store), store, features, supportbundlestest.NewFakeBundleService(), nil) + folderSvc := folderimpl.ProvideService(mock.New(), bus.ProvideBus(tracing.InitializeTracerForTest()), dashboardWriteStore, folderimpl.ProvideDashboardFolderStore(store), store, features, supportbundlestest.NewFakeBundleService(), nil, tracing.InitializeTracerForTest()) origNewGuardian := guardian.New guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true}) From 8961f392f0417a897c77d13078f14dacb7fa5860 Mon Sep 17 00:00:00 2001 From: Jeff Levin Date: Tue, 13 Aug 2024 02:58:00 -0800 Subject: [PATCH 040/229] add team_member index on user_id org_id (#91819) This pr adds a composite index on on the team_member table on user_id and org_id --- pkg/services/sqlstore/migrations/team_mig.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/services/sqlstore/migrations/team_mig.go b/pkg/services/sqlstore/migrations/team_mig.go index c292d7088f9..2104702516c 100644 --- a/pkg/services/sqlstore/migrations/team_mig.go +++ b/pkg/services/sqlstore/migrations/team_mig.go @@ -51,6 +51,7 @@ func addTeamMigrations(mg *Migrator) { {Cols: []string{"org_id"}}, {Cols: []string{"org_id", "team_id", "user_id"}, Type: UniqueIndex}, {Cols: []string{"team_id"}}, + {Cols: []string{"user_id", "org_id"}}, }, } @@ -73,4 +74,5 @@ func addTeamMigrations(mg *Migrator) { mg.AddMigration("Add column permission to team_member table", NewAddColumnMigration(teamMemberV1, &Column{ Name: "permission", Type: DB_SmallInt, Nullable: true, })) + mg.AddMigration("add unique index team_member_user_id_org_id", NewAddIndexMigration(teamMemberV1, teamMemberV1.Indices[3])) } From 71b56dbb957dee1d0d23411d6101d282b511cabc Mon Sep 17 00:00:00 2001 From: antonio <45235678+tonypowa@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:06:51 +0200 Subject: [PATCH 041/229] alerting tutorial: improve visibility of part 2 (#91795) * alerting tutorial: improve visibility of part 2 * changed wording * Update docs/sources/tutorials/alerting-get-started/index.md Co-authored-by: Jay Clifford <45856600+Jayclifford345@users.noreply.github.com> * applied suggestions * fixed admon * all pretty no pity --------- Co-authored-by: Jay Clifford <45856600+Jayclifford345@users.noreply.github.com> --- .../tutorials/alerting-get-started/index.md | 32 +++++++++++++++---- .../tutorials/grafana-fundamentals/index.md | 16 ++++++++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/docs/sources/tutorials/alerting-get-started/index.md b/docs/sources/tutorials/alerting-get-started/index.md index d1a5c91c9fa..1b7ea42a167 100644 --- a/docs/sources/tutorials/alerting-get-started/index.md +++ b/docs/sources/tutorials/alerting-get-started/index.md @@ -32,7 +32,21 @@ In this tutorial you will: - Set up an alert rule. - Receive firing and resolved alert notifications in a public webhook. -Check out [Part 2](http://grafana.com/tutorials/alerting-get-started-pt2/) if you want to learn more about alerts and notification routing. + + +{{< admonition type="tip" >}} + +Before you dive in, remember that you can [explore advanced topics like alert instances and notification routing](http://grafana.com/tutorials/alerting-get-started-pt2/) in the second part of this guide. + +{{< /admonition >}} + + + +{{< docs/ignore >}} + +> Before you dive in, remember that you can [explore advanced topics like alert instances and notification routing](http://grafana.com/tutorials/alerting-get-started-pt2/) in the second part of this guide. + +{{< /docs/ignore >}} @@ -265,16 +279,20 @@ By incrementing the threshold, the condition is no longer met, and after the eva ## Learn more -Your learning journey continues in [Part 2](http://grafana.com/tutorials/alerting-get-started-pt2/) where you will learn about alert instances and notification routing. + + +{{< admonition type="tip" >}} + +Advance your skills by exploring [alert instances and notification routing](http://grafana.com/tutorials/alerting-get-started-pt2/) in Part 2 of your learning journey. -## Summary +{{< /admonition >}} -In this tutorial, you have learned how to set up a contact point, create an alert, and send alert notifications to a public Webhook. By following these steps, you’ve gained a foundational understanding of how to leverage Grafana Alerting capabilities to monitor and respond to events of interest in your data. + -Feel free to experiment with different [contact points](https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/) to customize your alert notifications and discover the configuration that best suits your needs. +{{< docs/ignore >}} -If you run into any problems, you are welcome to post questions in our [Grafana Community forum](https://community.grafana.com/). +Advance your skills by exploring [alert instances and notification routing](http://grafana.com/tutorials/alerting-get-started-pt2/) in Part 2 of your learning journey. -Happy monitoring! +{{< /docs/ignore >}} diff --git a/docs/sources/tutorials/grafana-fundamentals/index.md b/docs/sources/tutorials/grafana-fundamentals/index.md index 5c4d710d4fe..f92da6381bb 100644 --- a/docs/sources/tutorials/grafana-fundamentals/index.md +++ b/docs/sources/tutorials/grafana-fundamentals/index.md @@ -389,6 +389,22 @@ Let's see how we can configure this. {{< figure src="/media/tutorials/grafana-alert-on-dashboard.png" alt="A panel in a Grafana dashboard with alerting and annotations configured" caption="Displaying Grafana Alerts on a dashboard" >}} + + +{{< admonition type="tip" >}} + +Check out our [advanced alerting tutorial](http://grafana.com/tutorials/alerting-get-started-pt2/) for more insights and tips. + +{{< /admonition >}} + + + +{{< docs/ignore >}} + +> Check out our [advanced alerting tutorial](http://grafana.com/tutorials/alerting-get-started-pt2/) for more insights and tips. + +{{< /docs/ignore >}} + ## Summary In this tutorial you learned about fundamental features of Grafana. To do so, we ran several Docker containers on your local machine. When you are ready to clean up this local tutorial environment, run the following command: From 149f02aebe2cb4454db66b44d8d55d955b3fb5b0 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Tue, 13 Aug 2024 13:27:23 +0200 Subject: [PATCH 042/229] Alerting: Add rule_group label to grafana_alerting_rule_group_rules metric (#88289) * Alerting: Add rule_group label to grafana_alerting_rule_group_rules metric (#62361) * Alerting: Delete rule group metrics when the rule group is deleted This commit addresses the issue where the GroupRules metric (a GaugeVec) keeps its value and is not deleted when an alert rule is removed from the rule registry. Previously, when an alert rule with orgID=1 was active, the metric was: grafana_alerting_rule_group_rules{org="1",state="active"} 1 However, after deleting this rule, subsequent calls to updateRulesMetrics did not update the gauge value, causing the metric to incorrectly remain at 1. The fix ensures that when updateRulesMetrics is called it also deletes the group rule metrics with the corresponding label values if needed. --- pkg/services/ngalert/metrics/scheduler.go | 3 +- pkg/services/ngalert/models/alert_rule.go | 5 + pkg/services/ngalert/schedule/metrics.go | 83 ++++- pkg/services/ngalert/schedule/schedule.go | 41 +- .../ngalert/schedule/schedule_unit_test.go | 351 ++++++++++++++++-- pkg/services/ngalert/schedule/testing.go | 2 +- 6 files changed, 427 insertions(+), 58 deletions(-) diff --git a/pkg/services/ngalert/metrics/scheduler.go b/pkg/services/ngalert/metrics/scheduler.go index 70056ea86c8..461f26871d5 100644 --- a/pkg/services/ngalert/metrics/scheduler.go +++ b/pkg/services/ngalert/metrics/scheduler.go @@ -121,7 +121,6 @@ func NewSchedulerMetrics(r prometheus.Registerer) *Scheduler { }, []string{"org"}, ), - // TODO: partition on rule group as well as tenant, similar to loki|cortex. GroupRules: promauto.With(r).NewGaugeVec( prometheus.GaugeOpts{ Namespace: Namespace, @@ -129,7 +128,7 @@ func NewSchedulerMetrics(r prometheus.Registerer) *Scheduler { Name: "rule_group_rules", Help: "The number of alert rules that are scheduled, both active and paused.", }, - []string{"org", "state"}, + []string{"org", "state", "rule_group"}, ), Groups: promauto.With(r).NewGaugeVec( prometheus.GaugeOpts{ diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 4faf0d52e65..127e85c1842 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -467,6 +467,11 @@ type AlertRuleGroupKey struct { RuleGroup string } +type AlertRuleGroupKeyWithFolderFullpath struct { + AlertRuleGroupKey + FolderFullpath string +} + func (k AlertRuleGroupKey) String() string { return fmt.Sprintf("{orgID: %d, namespaceUID: %s, groupName: %s}", k.OrgID, k.NamespaceUID, k.RuleGroup) } diff --git a/pkg/services/ngalert/schedule/metrics.go b/pkg/services/ngalert/schedule/metrics.go index 62e9e590794..73a2cb32708 100644 --- a/pkg/services/ngalert/schedule/metrics.go +++ b/pkg/services/ngalert/schedule/metrics.go @@ -32,16 +32,32 @@ func sortedUIDs(alertRules []*models.AlertRule) []string { return uids } +// updateRulesMetrics updates metrics for alert rules. +// Keeps a state in the schedule between calls to delete metrics for rules that are no longer present. func (sch *schedule) updateRulesMetrics(alertRules []*models.AlertRule) { - rulesPerOrg := make(map[int64]int64) // orgID -> count - orgsPaused := make(map[int64]int64) // orgID -> count - orgsNfSettings := make(map[int64]int64) // orgID -> count - groupsPerOrg := make(map[int64]map[string]struct{}) // orgID -> set of groups + rulesPerOrgFolderGroup := make(map[models.AlertRuleGroupKeyWithFolderFullpath]int64) // AlertRuleGroupKeyWithFolderFullpath -> count + rulesPerOrgFolderGroupPaused := make(map[models.AlertRuleGroupKeyWithFolderFullpath]int64) // AlertRuleGroupKeyWithFolderFullpath -> count + orgsNfSettings := make(map[int64]int64) // orgID -> count + groupsPerOrg := make(map[int64]map[string]struct{}) // orgID -> set of groups + + // Remember what orgs and alert groups we process in the current update metrics call, + // so we can delete metrics for orgs and groups that are no longer present in the new state. + updateMetricsForOrgsAndGroups := map[int64]map[models.AlertRuleGroupKeyWithFolderFullpath]struct{}{} // orgID -> set of AlertRuleGroupWithFolderTitle + for _, rule := range alertRules { - rulesPerOrg[rule.OrgID]++ + key := models.AlertRuleGroupKeyWithFolderFullpath{ + AlertRuleGroupKey: rule.GetGroupKey(), + FolderFullpath: sch.schedulableAlertRules.folderTitles[rule.GetFolderKey()], + } + rulesPerOrgFolderGroup[key]++ + + if _, ok := updateMetricsForOrgsAndGroups[rule.OrgID]; !ok { + updateMetricsForOrgsAndGroups[rule.OrgID] = make(map[models.AlertRuleGroupKeyWithFolderFullpath]struct{}) + } + updateMetricsForOrgsAndGroups[rule.OrgID][key] = struct{}{} if rule.IsPaused { - orgsPaused[rule.OrgID]++ + rulesPerOrgFolderGroupPaused[key]++ } if len(rule.NotificationSettings) > 0 { @@ -56,20 +72,59 @@ func (sch *schedule) updateRulesMetrics(alertRules []*models.AlertRule) { orgGroups[rule.RuleGroup] = struct{}{} } - for orgID, numRules := range rulesPerOrg { - numRulesPaused := orgsPaused[orgID] - numRulesNfSettings := orgsNfSettings[orgID] - sch.metrics.GroupRules.WithLabelValues(fmt.Sprint(orgID), metrics.AlertRuleActiveLabelValue).Set(float64(numRules - numRulesPaused)) - sch.metrics.GroupRules.WithLabelValues(fmt.Sprint(orgID), metrics.AlertRulePausedLabelValue).Set(float64(numRulesPaused)) - sch.metrics.SimpleNotificationRules.WithLabelValues(fmt.Sprint(orgID)).Set(float64(numRulesNfSettings)) + for key, numRules := range rulesPerOrgFolderGroup { + numRulesPaused := rulesPerOrgFolderGroupPaused[key] + ruleGroupLabelValue := makeRuleGroupLabelValue(key) + sch.metrics.GroupRules.WithLabelValues(fmt.Sprint(key.OrgID), metrics.AlertRuleActiveLabelValue, ruleGroupLabelValue).Set(float64(numRules - numRulesPaused)) + sch.metrics.GroupRules.WithLabelValues(fmt.Sprint(key.OrgID), metrics.AlertRulePausedLabelValue, ruleGroupLabelValue).Set(float64(numRulesPaused)) } - for orgID, groups := range groupsPerOrg { - sch.metrics.Groups.WithLabelValues(fmt.Sprint(orgID)).Set(float64(len(groups))) + for orgID := range updateMetricsForOrgsAndGroups { + sch.metrics.SimpleNotificationRules.WithLabelValues(fmt.Sprint(orgID)).Set(float64(orgsNfSettings[orgID])) + sch.metrics.Groups.WithLabelValues(fmt.Sprint(orgID)).Set(float64(len(groupsPerOrg[orgID]))) } // While these are the rules that we iterate over, at the moment there's no 100% guarantee that they'll be // scheduled as rules could be removed before we get a chance to evaluate them. sch.metrics.SchedulableAlertRules.Set(float64(len(alertRules))) sch.metrics.SchedulableAlertRulesHash.Set(float64(hashUIDs(alertRules))) + + // Delete metrics for rule groups and orgs that are no longer present in the new state + for orgID, alertRuleGroupsMap := range sch.lastUpdatedMetricsForOrgsAndGroups { + if orgOrGroupDeleted(updateMetricsForOrgsAndGroups, orgID, nil) { + sch.metrics.SimpleNotificationRules.DeleteLabelValues(fmt.Sprint(orgID)) + sch.metrics.Groups.DeleteLabelValues(fmt.Sprint(orgID)) + } + + for key := range alertRuleGroupsMap { + if orgOrGroupDeleted(updateMetricsForOrgsAndGroups, orgID, &key) { + ruleGroupLabelValue := makeRuleGroupLabelValue(key) + sch.metrics.GroupRules.DeleteLabelValues(fmt.Sprint(key.AlertRuleGroupKey.OrgID), metrics.AlertRuleActiveLabelValue, ruleGroupLabelValue) + sch.metrics.GroupRules.DeleteLabelValues(fmt.Sprint(key.AlertRuleGroupKey.OrgID), metrics.AlertRulePausedLabelValue, ruleGroupLabelValue) + } + } + } + + // update the call state + sch.lastUpdatedMetricsForOrgsAndGroups = updateMetricsForOrgsAndGroups +} + +// makeRuleGroupLabelValue returns a string that can be used as a label (rule_group) value for alert rule group metrics. +func makeRuleGroupLabelValue(key models.AlertRuleGroupKeyWithFolderFullpath) string { + return fmt.Sprintf("%s;%s", key.FolderFullpath, key.AlertRuleGroupKey.RuleGroup) +} + +// orgOrGroupDeleted returns true if the org or group is no longer present in the new update metrics state. +func orgOrGroupDeleted(updateMetrics map[int64]map[models.AlertRuleGroupKeyWithFolderFullpath]struct{}, orgID int64, alertRuleGroupKey *models.AlertRuleGroupKeyWithFolderFullpath) bool { + if _, ok := updateMetrics[orgID]; !ok { + return true + } + + if alertRuleGroupKey != nil { + if _, ok := updateMetrics[orgID][*alertRuleGroupKey]; !ok { + return true + } + } + + return false } diff --git a/pkg/services/ngalert/schedule/schedule.go b/pkg/services/ngalert/schedule/schedule.go index 4eeab054a57..1772cbf8bb5 100644 --- a/pkg/services/ngalert/schedule/schedule.go +++ b/pkg/services/ngalert/schedule/schedule.go @@ -87,6 +87,10 @@ type schedule struct { featureToggles featuremgmt.FeatureToggles metrics *metrics.Scheduler + // lastUpdatedMetricsForOrgsAndGroups contains AlertRuleGroupKeyWithFolderFullpaths that + // were passed to updateRulesMetrics in the current tick. This is used to + // delete metrics for the rules/groups that are not longer present. + lastUpdatedMetricsForOrgsAndGroups map[int64]map[ngmodels.AlertRuleGroupKeyWithFolderFullpath]struct{} // orgID -> set of AlertRuleGroupKeyWithFolderFullpath alertsSender AlertsSender minRuleInterval time.Duration @@ -130,24 +134,25 @@ func NewScheduler(cfg SchedulerCfg, stateManager *state.Manager) *schedule { } sch := schedule{ - registry: newRuleRegistry(), - maxAttempts: cfg.MaxAttempts, - clock: cfg.C, - baseInterval: cfg.BaseInterval, - log: cfg.Log, - evaluatorFactory: cfg.EvaluatorFactory, - ruleStore: cfg.RuleStore, - metrics: cfg.Metrics, - appURL: cfg.AppURL, - disableGrafanaFolder: cfg.DisableGrafanaFolder, - jitterEvaluations: cfg.JitterEvaluations, - featureToggles: cfg.FeatureToggles, - stateManager: stateManager, - minRuleInterval: cfg.MinRuleInterval, - schedulableAlertRules: alertRulesRegistry{rules: make(map[ngmodels.AlertRuleKey]*ngmodels.AlertRule)}, - alertsSender: cfg.AlertSender, - tracer: cfg.Tracer, - recordingWriter: cfg.RecordingWriter, + registry: newRuleRegistry(), + maxAttempts: cfg.MaxAttempts, + clock: cfg.C, + baseInterval: cfg.BaseInterval, + log: cfg.Log, + evaluatorFactory: cfg.EvaluatorFactory, + ruleStore: cfg.RuleStore, + metrics: cfg.Metrics, + lastUpdatedMetricsForOrgsAndGroups: make(map[int64]map[ngmodels.AlertRuleGroupKeyWithFolderFullpath]struct{}), + appURL: cfg.AppURL, + disableGrafanaFolder: cfg.DisableGrafanaFolder, + jitterEvaluations: cfg.JitterEvaluations, + featureToggles: cfg.FeatureToggles, + stateManager: stateManager, + minRuleInterval: cfg.MinRuleInterval, + schedulableAlertRules: alertRulesRegistry{rules: make(map[ngmodels.AlertRuleKey]*ngmodels.AlertRule)}, + alertsSender: cfg.AlertSender, + tracer: cfg.Tracer, + recordingWriter: cfg.RecordingWriter, } return &sch diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index 22e44150a4c..7e94bee11a1 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -107,6 +107,8 @@ func TestProcessTicks(t *testing.T) { alertRule1 := gen.With(gen.WithOrgID(mainOrgID), gen.WithInterval(cfg.BaseInterval), gen.WithTitle("rule-1")).GenerateRef() ruleStore.PutRule(ctx, alertRule1) + folderWithRuleGroup1 := fmt.Sprintf("%s;%s", ruleStore.getNamespaceTitle(alertRule1.NamespaceUID), alertRule1.RuleGroup) + t.Run("on 1st tick alert rule should be evaluated", func(t *testing.T) { tick = tick.Add(cfg.BaseInterval) @@ -124,9 +126,9 @@ func TestProcessTicks(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, both active and paused. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",state="active"} 1 - grafana_alerting_rule_group_rules{org="%[1]d",state="paused"} 0 - `, alertRule1.OrgID) + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="paused"} 0 + `, alertRule1.OrgID, folderWithRuleGroup1) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -136,6 +138,8 @@ func TestProcessTicks(t *testing.T) { alertRule2 := gen.With(gen.WithOrgID(mainOrgID), gen.WithInterval(3*cfg.BaseInterval), gen.WithTitle("rule-2")).GenerateRef() ruleStore.PutRule(ctx, alertRule2) + folderWithRuleGroup2 := fmt.Sprintf("%s;%s", ruleStore.getNamespaceTitle(alertRule2.NamespaceUID), alertRule2.RuleGroup) + t.Run("on 2nd tick first alert rule should be evaluated", func(t *testing.T) { tick = tick.Add(cfg.BaseInterval) scheduled, stopped, updated := sched.processTick(ctx, dispatcherGroup, tick) @@ -148,13 +152,15 @@ func TestProcessTicks(t *testing.T) { assertEvalRun(t, evalAppliedCh, tick, alertRule1.GetKey()) }) - t.Run("after 2nd tick rule metrics should report two active alert rules", func(t *testing.T) { + t.Run("after 2nd tick rule metrics should report two active alert rules in two groups", func(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, both active and paused. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",state="active"} 2 - grafana_alerting_rule_group_rules{org="%[1]d",state="paused"} 0 - `, alertRule1.OrgID) + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="paused"} 0 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="paused"} 0 + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -204,13 +210,15 @@ func TestProcessTicks(t *testing.T) { assertEvalRun(t, evalAppliedCh, tick, alertRule1.GetKey()) }) - t.Run("after 5th tick rule metrics should report one active and one paused alert rules", func(t *testing.T) { + t.Run("after 5th tick rule metrics should report one active and one paused alert rules in two groups", func(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, both active and paused. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",state="active"} 1 - grafana_alerting_rule_group_rules{org="%[1]d",state="paused"} 1 - `, alertRule1.OrgID) + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active"} 0 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="paused"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="paused"} 0 + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -237,13 +245,15 @@ func TestProcessTicks(t *testing.T) { assertEvalRun(t, evalAppliedCh, tick, keys...) }) - t.Run("after 6th tick rule metrics should report two paused alert rules", func(t *testing.T) { + t.Run("after 6th tick rule metrics should report two paused alert rules in two groups", func(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, both active and paused. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",state="active"} 0 - grafana_alerting_rule_group_rules{org="%[1]d",state="paused"} 2 - `, alertRule1.OrgID) + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active"} 0 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="paused"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="active"} 0 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="paused"} 1 + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -265,13 +275,15 @@ func TestProcessTicks(t *testing.T) { assertEvalRun(t, evalAppliedCh, tick, alertRule1.GetKey()) }) - t.Run("after 7th tick rule metrics should report two active alert rules", func(t *testing.T) { + t.Run("after 7th tick rule metrics should report two active alert rules in two groups", func(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, both active and paused. # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",state="active"} 2 - grafana_alerting_rule_group_rules{org="%[1]d",state="paused"} 0 - `, alertRule1.OrgID) + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="paused"} 0 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="paused"} 0 + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -295,10 +307,10 @@ func TestProcessTicks(t *testing.T) { t.Run("after 8th tick rule metrics should report one active alert rule", func(t *testing.T) { expectedMetric := fmt.Sprintf( `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, both active and paused. - # TYPE grafana_alerting_rule_group_rules gauge - grafana_alerting_rule_group_rules{org="%[1]d",state="active"} 1 - grafana_alerting_rule_group_rules{org="%[1]d",state="paused"} 0 - `, alertRule1.OrgID) + # TYPE grafana_alerting_rule_group_rules gauge + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="paused"} 0 + `, alertRule2.OrgID, folderWithRuleGroup2) err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") require.NoError(t, err) @@ -398,6 +410,299 @@ func TestProcessTicks(t *testing.T) { }) } +func TestSchedule_updateRulesMetrics(t *testing.T) { + ruleStore := newFakeRulesStore() + reg := prometheus.NewPedanticRegistry() + sch := setupScheduler(t, ruleStore, nil, reg, nil, nil) + ctx := context.Background() + const firstOrgID int64 = 1 + + t.Run("grafana_alerting_rule_group_rules metric should reflect the current state", func(t *testing.T) { + // Without any rules there are no metrics + t.Run("it should not show metrics", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{}) + + expectedMetric := "" + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") + require.NoError(t, err) + }) + + alertRule1 := models.RuleGen.With(models.RuleGen.WithOrgID(firstOrgID)).GenerateRef() + folderWithRuleGroup1 := fmt.Sprintf("%s;%s", ruleStore.getNamespaceTitle(alertRule1.NamespaceUID), alertRule1.RuleGroup) + ruleStore.PutRule(ctx, alertRule1) + + _, err := sch.updateSchedulableAlertRules(ctx) // to update folderTitles + require.NoError(t, err) + + t.Run("it should show one active rule in a single group", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRule1}) + + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, both active and paused. + # TYPE grafana_alerting_rule_group_rules gauge + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="paused"} 0 + `, alertRule1.OrgID, folderWithRuleGroup1) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") + require.NoError(t, err) + }) + + // Add a new rule alertRule2 and check that it is reflected in the metrics + alertRule2 := models.RuleGen.With(models.RuleGen.WithOrgID(firstOrgID)).GenerateRef() + folderWithRuleGroup2 := fmt.Sprintf("%s;%s", ruleStore.getNamespaceTitle(alertRule2.NamespaceUID), alertRule2.RuleGroup) + ruleStore.PutRule(ctx, alertRule2) + + _, err = sch.updateSchedulableAlertRules(ctx) // to update folderTitles + require.NoError(t, err) + + t.Run("it should show two active rules in two groups", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRule1, alertRule2}) + + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, both active and paused. + # TYPE grafana_alerting_rule_group_rules gauge + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="paused"} 0 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="paused"} 0 + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") + require.NoError(t, err) + }) + + // Now remove the alertRule2 + t.Run("it should show one active rules in one groups", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRule1, alertRule2}) + + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_rule_group_rules The number of alert rules that are scheduled, both active and paused. + # TYPE grafana_alerting_rule_group_rules gauge + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[2]s",state="paused"} 0 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="active"} 1 + grafana_alerting_rule_group_rules{org="%[1]d",rule_group="%[3]s",state="paused"} 0 + `, alertRule1.OrgID, folderWithRuleGroup1, folderWithRuleGroup2) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") + require.NoError(t, err) + }) + + // and remove the alertRule1 so there should be no metrics now + t.Run("it should show one active rules in one groups", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{}) + + expectedMetric := "" + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_group_rules") + require.NoError(t, err) + }) + }) + + t.Run("rule_groups metric should reflect the current state", func(t *testing.T) { + const firstOrgID int64 = 1 + const secondOrgID int64 = 2 + + // Without any rules there are no metrics + t.Run("it should not show metrics", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{}) + + expectedMetric := "" + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_groups") + require.NoError(t, err) + }) + + alertRule1 := models.RuleGen.With(models.RuleGen.WithOrgID(firstOrgID)).GenerateRef() + + t.Run("it should show one rule group in a single org", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRule1}) + + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_rule_groups The number of alert rule groups + # TYPE grafana_alerting_rule_groups gauge + grafana_alerting_rule_groups{org="%[1]d"} 1 + `, alertRule1.OrgID) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_groups") + require.NoError(t, err) + }) + + alertRule2 := models.RuleGen.With(models.RuleGen.WithOrgID(secondOrgID)).GenerateRef() + + t.Run("it should show two rule groups in two orgs", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRule1, alertRule2}) + + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_rule_groups The number of alert rule groups + # TYPE grafana_alerting_rule_groups gauge + grafana_alerting_rule_groups{org="%[1]d"} 1 + grafana_alerting_rule_groups{org="%[2]d"} 1 + `, alertRule1.OrgID, alertRule2.OrgID) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_groups") + require.NoError(t, err) + }) + + t.Run("when the first rule is removed it should show one rule group", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRule2}) + + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_rule_groups The number of alert rule groups + # TYPE grafana_alerting_rule_groups gauge + grafana_alerting_rule_groups{org="%[1]d"} 1 + `, alertRule2.OrgID) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_groups") + require.NoError(t, err) + }) + }) + + t.Run("simple_routing_rules metric should reflect the current state", func(t *testing.T) { + const firstOrgID int64 = 1 + const secondOrgID int64 = 2 + + // Has no NotificationSettings, should not be in the metrics + alertRuleWithoutNotificationSettings := models.RuleGen.With( + models.RuleGen.WithOrgID(firstOrgID), + models.RuleGen.WithNoNotificationSettings(), + ).GenerateRef() + + // Without any rules there are no metrics + t.Run("it should not show metrics", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRuleWithoutNotificationSettings}) + + // Because alertRuleWithoutNotificationSettings.orgID is present, + // the metric is also present but set to 0 because the org has no rules with NotificationSettings. + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_simple_routing_rules The number of alert rules using simplified routing. + # TYPE grafana_alerting_simple_routing_rules gauge + grafana_alerting_simple_routing_rules{org="%[1]d"} 0 + `, alertRuleWithoutNotificationSettings.OrgID) + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_simple_routing_rules") + require.NoError(t, err) + }) + + alertRule1 := models.RuleGen.With( + models.RuleGen.WithOrgID(firstOrgID), + models.RuleGen.WithNotificationSettingsGen(models.NotificationSettingsGen()), + ).GenerateRef() + + t.Run("it should show one rule in a single org", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRuleWithoutNotificationSettings, alertRule1}) + + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_simple_routing_rules The number of alert rules using simplified routing. + # TYPE grafana_alerting_simple_routing_rules gauge + grafana_alerting_simple_routing_rules{org="%[1]d"} 1 + `, alertRule1.OrgID) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_simple_routing_rules") + require.NoError(t, err) + }) + + alertRule2 := models.RuleGen.With( + models.RuleGen.WithOrgID(secondOrgID), + models.RuleGen.WithNotificationSettingsGen(models.NotificationSettingsGen()), + ).GenerateRef() + + t.Run("it should show two rules in two orgs", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRuleWithoutNotificationSettings, alertRule1, alertRule2}) + + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_simple_routing_rules The number of alert rules using simplified routing. + # TYPE grafana_alerting_simple_routing_rules gauge + grafana_alerting_simple_routing_rules{org="%[1]d"} 1 + grafana_alerting_simple_routing_rules{org="%[2]d"} 1 + `, alertRule1.OrgID, alertRule2.OrgID) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_simple_routing_rules") + require.NoError(t, err) + }) + + t.Run("after removing one of the rules it should show one present rule and two org", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRuleWithoutNotificationSettings, alertRule2}) + + // Because alertRuleWithoutNotificationSettings.orgID is present, + // the metric is also present but set to 0 because the org has no rules with NotificationSettings. + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_simple_routing_rules The number of alert rules using simplified routing. + # TYPE grafana_alerting_simple_routing_rules gauge + grafana_alerting_simple_routing_rules{org="%[1]d"} 0 + grafana_alerting_simple_routing_rules{org="%[2]d"} 1 + `, alertRuleWithoutNotificationSettings.OrgID, alertRule2.OrgID) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_simple_routing_rules") + require.NoError(t, err) + }) + + t.Run("after removing all rules it should not show any metrics", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{}) + + expectedMetric := "" + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_simple_routing_rules") + require.NoError(t, err) + }) + }) + + t.Run("rule_groups metric should reflect the current state", func(t *testing.T) { + const firstOrgID int64 = 1 + const secondOrgID int64 = 2 + + // Without any rules there are no metrics + t.Run("it should not show metrics", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{}) + + expectedMetric := "" + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_groups") + require.NoError(t, err) + }) + + alertRule1 := models.RuleGen.With(models.RuleGen.WithOrgID(firstOrgID)).GenerateRef() + + t.Run("it should show one rule group in a single org", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRule1}) + + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_rule_groups The number of alert rule groups + # TYPE grafana_alerting_rule_groups gauge + grafana_alerting_rule_groups{org="%[1]d"} 1 + `, alertRule1.OrgID) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_groups") + require.NoError(t, err) + }) + + alertRule2 := models.RuleGen.With(models.RuleGen.WithOrgID(secondOrgID)).GenerateRef() + + t.Run("it should show two rule groups in two orgs", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRule1, alertRule2}) + + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_rule_groups The number of alert rule groups + # TYPE grafana_alerting_rule_groups gauge + grafana_alerting_rule_groups{org="%[1]d"} 1 + grafana_alerting_rule_groups{org="%[2]d"} 1 + `, alertRule1.OrgID, alertRule2.OrgID) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_groups") + require.NoError(t, err) + }) + + t.Run("when the first rule is removed it should show one rule group", func(t *testing.T) { + sch.updateRulesMetrics([]*models.AlertRule{alertRule2}) + + expectedMetric := fmt.Sprintf( + `# HELP grafana_alerting_rule_groups The number of alert rule groups + # TYPE grafana_alerting_rule_groups gauge + grafana_alerting_rule_groups{org="%[1]d"} 1 + `, alertRule2.OrgID) + + err := testutil.GatherAndCompare(reg, bytes.NewBufferString(expectedMetric), "grafana_alerting_rule_groups") + require.NoError(t, err) + }) + }) +} + func TestSchedule_deleteAlertRule(t *testing.T) { t.Run("when rule exists", func(t *testing.T) { t.Run("it should stop evaluation loop and remove the controller from registry", func(t *testing.T) { diff --git a/pkg/services/ngalert/schedule/testing.go b/pkg/services/ngalert/schedule/testing.go index d182329cf16..6e0145e44a9 100644 --- a/pkg/services/ngalert/schedule/testing.go +++ b/pkg/services/ngalert/schedule/testing.go @@ -66,7 +66,7 @@ func (f *fakeRulesStore) GetAlertRulesForScheduling(ctx context.Context, query * query.ResultFoldersTitles = map[models.FolderKey]string{} for _, rule := range f.rules { query.ResultRules = append(query.ResultRules, rule) - key := models.FolderKey{OrgID: rule.OrgID, UID: rule.UID} + key := models.FolderKey{OrgID: rule.OrgID, UID: rule.NamespaceUID} query.ResultFoldersTitles[key] = f.getNamespaceTitle(rule.NamespaceUID) } return nil From 735954386f81f9761aa481785151ca13cea74a5a Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Tue, 13 Aug 2024 12:56:13 +0100 Subject: [PATCH 043/229] Alerting: Consolidate contact points dropdown and add filter in alert rules (#91690) Co-authored-by: Gilles De Mey Co-authored-by: Konrad Lalik Co-authored-by: Sonia Aguilar --- .betterer.results | 3 - .../unified/NotificationPolicies.test.tsx | 6 +- .../alerting/unified/NotificationPolicies.tsx | 4 +- .../alert-groups/AlertGroupFilter.tsx | 1 + .../contact-points/ContactPoint.tsx | 30 +++- .../ContactPointSelector.tsx | 133 +++++++++++----- .../EditDefaultPolicyForm.test.tsx | 41 +++-- .../EditDefaultPolicyForm.tsx | 29 ++-- .../EditNotificationPolicyForm.test.tsx | 27 ++-- .../EditNotificationPolicyForm.tsx | 41 ++--- .../notification-policies/Filters.tsx | 37 ++--- .../notification-policies/Modals.tsx | 20 +-- .../components/notification-policies/utils.ts | 19 +++ .../grafanaAppReceivers/grafanaApp.ts | 40 ----- .../receivers/grafanaAppReceivers/types.ts | 6 - .../simplifiedRouting/AlertManagerRouting.tsx | 30 +--- .../contactPoint/ContactPointSelector.tsx | 148 +++--------------- .../unified/components/rules/RulesFilter.tsx | 45 +++++- .../unified/hooks/useFilteredRules.ts | 22 ++- .../mocks/server/handlers/alertmanagers.ts | 21 ++- .../unified/search/rulesSearchParser.test.ts | 20 ++- .../unified/search/rulesSearchParser.ts | 6 + .../alerting/unified/search/search.grammar | 7 +- .../alerting/unified/search/search.js | 35 +++-- .../alerting/unified/search/search.terms.js | 6 +- .../alerting/unified/search/searchParser.ts | 2 + .../alerting/unified/utils/amroutes.ts | 13 -- public/locales/en-US/grafana.json | 3 + public/locales/pseudo-LOCALE/grafana.json | 3 + 29 files changed, 378 insertions(+), 420 deletions(-) create mode 100644 public/app/features/alerting/unified/components/notification-policies/utils.ts delete mode 100644 public/app/features/alerting/unified/components/receivers/grafanaAppReceivers/grafanaApp.ts diff --git a/.betterer.results b/.betterer.results index 576dfc83f2e..f6d6df2a211 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1769,9 +1769,6 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] ], - "public/app/features/alerting/unified/components/notification-policies/ContactPointSelector.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] - ], "public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"] diff --git a/public/app/features/alerting/unified/NotificationPolicies.test.tsx b/public/app/features/alerting/unified/NotificationPolicies.test.tsx index 466e034cd4c..f9cc962e05e 100644 --- a/public/app/features/alerting/unified/NotificationPolicies.test.tsx +++ b/public/app/features/alerting/unified/NotificationPolicies.test.tsx @@ -4,6 +4,7 @@ import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector' import { DataSourceSrv, setDataSourceSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { AlertManagerCortexConfig, AlertManagerDataSourceJsonData, @@ -19,7 +20,6 @@ import NotificationPolicies, { findRoutesMatchingFilters } from './NotificationP import { fetchAlertManagerConfig, fetchStatus, updateAlertManagerConfig } from './api/alertmanager'; import { alertmanagerApi } from './api/alertmanagerApi'; import { discoverAlertmanagerFeatures } from './api/buildInfo'; -import * as grafanaApp from './components/receivers/grafanaAppReceivers/grafanaApp'; import { MockDataSourceSrv, mockDataSource, someCloudAlertManagerConfig, someCloudAlertManagerStatus } from './mocks'; import { defaultGroupBy } from './utils/amroutes'; import { getAllDataSources } from './utils/config'; @@ -45,7 +45,8 @@ const mocks = { }, contextSrv: jest.mocked(contextSrv), }; -const useGetGrafanaReceiverTypeCheckerMock = jest.spyOn(grafanaApp, 'useGetGrafanaReceiverTypeChecker'); + +setupMswServer(); const renderNotificationPolicies = (alertManagerSourceName?: string) => { return render(, { @@ -195,7 +196,6 @@ describe('NotificationPolicies', () => { mocks.contextSrv.evaluatePermission.mockImplementation(() => []); mocks.api.discoverAlertmanagerFeatures.mockResolvedValue({ lazyConfigInit: false }); setDataSourceSrv(new MockDataSourceSrv(dataSources)); - useGetGrafanaReceiverTypeCheckerMock.mockReturnValue(() => undefined); }); afterEach(() => { diff --git a/public/app/features/alerting/unified/NotificationPolicies.tsx b/public/app/features/alerting/unified/NotificationPolicies.tsx index e58f634d258..ba1f5025330 100644 --- a/public/app/features/alerting/unified/NotificationPolicies.tsx +++ b/public/app/features/alerting/unified/NotificationPolicies.tsx @@ -193,10 +193,9 @@ const AmRoutes = () => { } // edit, add, delete modals - const [addModal, openAddModal, closeAddModal] = useAddPolicyModal(receivers, handleAdd, updatingTree); + const [addModal, openAddModal, closeAddModal] = useAddPolicyModal(handleAdd, updatingTree); const [editModal, openEditModal, closeEditModal] = useEditPolicyModal( selectedAlertmanager ?? '', - receivers, handleSave, updatingTree ); @@ -253,7 +252,6 @@ const AmRoutes = () => { {rootRoute && ( { groupBy: null, queryString: null, alertState: null, + contactPoint: null, }); setTimeout(() => setFilterKey(filterKey + 1), 100); }; diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx index f8b353a1059..60d2663d918 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoint.tsx @@ -8,7 +8,6 @@ import { Trans } from 'app/core/internationalization'; import { PrimaryText } from 'app/features/alerting/unified/components/common/TextVariants'; import { ContactPointHeader } from 'app/features/alerting/unified/components/contact-points/ContactPointHeader'; import { receiverTypeNames } from 'app/plugins/datasource/alertmanager/consts'; -import { GrafanaManagedReceiverConfig } from 'app/plugins/datasource/alertmanager/types'; import { GrafanaNotifierType, NotifierStatus } from 'app/types/alerting'; import { INTEGRATION_ICONS } from '../../types/contact-points'; @@ -152,37 +151,56 @@ interface ContactPointReceiverMetadata { } type ContactPointReceiverSummaryProps = { - receivers: GrafanaManagedReceiverConfig[]; + receivers: ReceiverConfigWithMetadata[]; + limit?: number; }; /** * This summary is used when we're dealing with non-Grafana managed alertmanager since they * don't have any metadata worth showing other than a summary of what types are configured for the contact point */ -export const ContactPointReceiverSummary = ({ receivers }: ContactPointReceiverSummaryProps) => { +export const ContactPointReceiverSummary = ({ receivers, limit }: ContactPointReceiverSummaryProps) => { + // limit for how many integrations are rendered + const INTEGRATIONS_LIMIT = limit ?? Number.MAX_VALUE; const countByType = groupBy(receivers, (receiver) => receiver.type); + const numberOfUniqueIntegrations = size(countByType); + const integrationsShown = Object.entries(countByType).slice(0, INTEGRATIONS_LIMIT); + const numberOfIntegrationsNotShown = numberOfUniqueIntegrations - INTEGRATIONS_LIMIT; + return ( - {Object.entries(countByType).map(([type, receivers], index) => { + {integrationsShown.map(([type, receivers], index) => { const iconName = INTEGRATION_ICONS[type]; const receiverName = receiverTypeNames[type] ?? upperFirst(type); const isLastItem = size(countByType) - 1 === index; + // Pick the first integration of the grouped receivers, since they should all be the same type + // e.g. if we have multiple Oncall, they _should_ all have the same plugin metadata, + // so we can just use the first one for additional display purposes + const receiver = receivers[0]; return ( + {receiver[RECEIVER_PLUGIN_META_KEY]?.icon && ( + {receiver[RECEIVER_PLUGIN_META_KEY]?.title} + )} {iconName && } - + {receiverName} {receivers.length > 1 && receivers.length} - + {!isLastItem && '⋅'} ); })} + {numberOfIntegrationsNotShown > 0 && {`+${numberOfIntegrationsNotShown} more`}} ); diff --git a/public/app/features/alerting/unified/components/notification-policies/ContactPointSelector.tsx b/public/app/features/alerting/unified/components/notification-policies/ContactPointSelector.tsx index 29d496d1eec..bc0a9a6ec0f 100644 --- a/public/app/features/alerting/unified/components/notification-policies/ContactPointSelector.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/ContactPointSelector.tsx @@ -1,52 +1,115 @@ -import { SelectableValue } from '@grafana/data'; -import { Select, SelectCommonProps, Text, Stack } from '@grafana/ui'; +import { css, cx, keyframes } from '@emotion/css'; +import { useMemo, useState } from 'react'; + +import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { Select, SelectCommonProps, Stack, Alert, IconButton, Text, useStyles2 } from '@grafana/ui'; +import { ContactPointReceiverSummary } from 'app/features/alerting/unified/components/contact-points/ContactPoint'; import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext'; -import { RECEIVER_META_KEY, RECEIVER_PLUGIN_META_KEY } from '../contact-points/constants'; import { useContactPointsWithStatus } from '../contact-points/useContactPoints'; -import { ReceiverConfigWithMetadata } from '../contact-points/utils'; +import { ContactPointWithMetadata } from '../contact-points/utils'; -export const ContactPointSelector = (props: SelectCommonProps) => { - const { selectedAlertmanager } = useAlertmanager(); - const { contactPoints, isLoading, error } = useContactPointsWithStatus({ alertmanager: selectedAlertmanager! }); +const MAX_CONTACT_POINTS_RENDERED = 500; - // TODO error handling - if (error) { - return Failed to load contact points; - } +// Mock sleep method, as fetching receivers is very fast and may seem like it hasn't occurred +const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); +const LOADING_SPINNER_DURATION = 1000; - const options: Array> = contactPoints.map((contactPoint) => { +type ContactPointSelectorProps = { + selectProps: SelectCommonProps; + showRefreshButton?: boolean; + /** Name of a contact point to optionally find and set as the preset value on the dropdown */ + selectedContactPointName?: string | null; +}; + +export const ContactPointSelector = ({ + selectProps, + showRefreshButton, + selectedContactPointName, +}: ContactPointSelectorProps) => { + const { selectedAlertmanager } = useAlertmanager(); + const { contactPoints, isLoading, error, refetch } = useContactPointsWithStatus({ + alertmanager: selectedAlertmanager!, + }); + const [loaderSpinning, setLoaderSpinning] = useState(false); + const styles = useStyles2(getStyles); + + const options: Array> = contactPoints.map((contactPoint) => { return { label: contactPoint.name, - value: contactPoint.name, - component: () => , + value: contactPoint, + component: () => ( + + + + ), }; }); - return MAX_CONTACT_POINTS_RENDERED} + options={options} + value={matchedContactPoint} + {...selectProps} + isLoading={isLoading} + /> + {showRefreshButton && ( + + )} ); }; + +const rotation = keyframes({ + from: { + transform: 'rotate(0deg)', + }, + to: { + transform: 'rotate(720deg)', + }, +}); + +const getStyles = (theme: GrafanaTheme2) => ({ + refreshButton: css({ + color: theme.colors.text.secondary, + cursor: 'pointer', + borderRadius: theme.shape.radius.circle, + overflow: 'hidden', + }), + loading: css({ + pointerEvents: 'none', + [theme.transitions.handleMotion('no-preference')]: { + animation: `${rotation} 2s infinite linear`, + }, + [theme.transitions.handleMotion('reduce')]: { + animation: `${rotation} 6s infinite linear`, + }, + }), +}); diff --git a/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.test.tsx b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.test.tsx index 3c16817ebb2..e7ed08151a5 100644 --- a/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.test.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.test.tsx @@ -3,12 +3,14 @@ import { render } from 'test/test-utils'; import { byRole } from 'testing-library-selector'; import { Button } from '@grafana/ui'; +import { setupMswServer } from 'app/features/alerting/unified/mockApi'; +import { grantUserPermissions } from 'app/features/alerting/unified/mocks'; +import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext'; +import { AccessControlAction } from 'app/types'; import { RouteWithID } from '../../../../../plugins/datasource/alertmanager/types'; -import * as grafanaApp from '../../components/receivers/grafanaAppReceivers/grafanaApp'; import { FormAmRoute } from '../../types/amroutes'; import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; -import { AmRouteReceiver } from '../receivers/grafanaAppReceivers/types'; import { AmRootRouteForm } from './EditDefaultPolicyForm'; @@ -20,12 +22,15 @@ const ui = { groupIntervalInput: byRole('textbox', { name: /Group interval/ }), repeatIntervalInput: byRole('textbox', { name: /Repeat interval/ }), }; - -const useGetGrafanaReceiverTypeCheckerMock = jest.spyOn(grafanaApp, 'useGetGrafanaReceiverTypeChecker'); -useGetGrafanaReceiverTypeCheckerMock.mockReturnValue(() => undefined); - +setupMswServer(); // TODO Default and Notification policy form should be unified so we don't need to maintain two almost identical forms describe('EditDefaultPolicyForm', function () { + beforeEach(() => { + grantUserPermissions([ + AccessControlAction.AlertingNotificationsRead, + AccessControlAction.AlertingNotificationsWrite, + ]); + }); describe('Timing options', function () { it('should render prometheus duration strings in form inputs', async function () { const { user } = renderRouteForm({ @@ -47,7 +52,6 @@ describe('EditDefaultPolicyForm', function () { id: '0', receiver: 'default', }, - [{ value: 'default', label: 'Default' }], onSubmit ); @@ -78,7 +82,6 @@ describe('EditDefaultPolicyForm', function () { id: '0', receiver: 'default', }, - [{ value: 'default', label: 'Default' }], onSubmit ); @@ -105,7 +108,6 @@ describe('EditDefaultPolicyForm', function () { group_interval: '2d4h30m35s', repeat_interval: '1w2d6h', }, - [{ value: 'default', label: 'Default' }], onSubmit ); @@ -128,18 +130,15 @@ describe('EditDefaultPolicyForm', function () { }); }); -function renderRouteForm( - route: RouteWithID, - receivers: AmRouteReceiver[] = [], - onSubmit: (route: Partial) => void = noop -) { +function renderRouteForm(route: RouteWithID, onSubmit: (route: Partial) => void = noop) { return render( - Update default policy} - onSubmit={onSubmit} - receivers={receivers} - route={route} - /> + + Update default policy} + onSubmit={onSubmit} + route={route} + /> + ); } diff --git a/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx index 5f75bfcde1c..bfba851da73 100644 --- a/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/EditDefaultPolicyForm.tsx @@ -1,7 +1,9 @@ import { ReactNode, useState } from 'react'; import { useForm, Controller } from 'react-hook-form'; -import { Collapse, Field, Link, MultiSelect, Select, useStyles2 } from '@grafana/ui'; +import { Collapse, Field, Link, MultiSelect, useStyles2 } from '@grafana/ui'; +import { ContactPointSelector } from 'app/features/alerting/unified/components/notification-policies/ContactPointSelector'; +import { handleContactPointSelect } from 'app/features/alerting/unified/components/notification-policies/utils'; import { RouteWithID } from 'app/plugins/datasource/alertmanager/types'; import { FormAmRoute } from '../../types/amroutes'; @@ -9,14 +11,12 @@ import { amRouteToFormAmRoute, commonGroupByOptions, mapMultiSelectValueToStrings, - mapSelectValueToString, promDurationValidator, repeatIntervalValidator, stringsToSelectableValues, stringToSelectableValue, } from '../../utils/amroutes'; import { makeAMLink } from '../../utils/misc'; -import { AmRouteReceiver } from '../receivers/grafanaAppReceivers/types'; import { PromDurationInput } from './PromDurationInput'; import { getFormStyles } from './formStyles'; @@ -26,17 +26,10 @@ export interface AmRootRouteFormProps { alertManagerSourceName: string; actionButtons: ReactNode; onSubmit: (route: Partial) => void; - receivers: AmRouteReceiver[]; route: RouteWithID; } -export const AmRootRouteForm = ({ - actionButtons, - alertManagerSourceName, - onSubmit, - receivers, - route, -}: AmRootRouteFormProps) => { +export const AmRootRouteForm = ({ actionButtons, alertManagerSourceName, onSubmit, route }: AmRootRouteFormProps) => { const styles = useStyles2(getFormStyles); const [isTimingOptionsExpanded, setIsTimingOptionsExpanded] = useState(false); const [groupByOptions, setGroupByOptions] = useState(stringsToSelectableValues(route.group_by)); @@ -62,13 +55,13 @@ export const AmRootRouteForm = ({ <>
( - onChange(mapSelectValueToString(value))} - options={receiversWithOnCallOnTop} - isClearable + render={({ field: { onChange, ref, value, ...field } }) => ( + handleContactPointSelect(value, onChange), + isClearable: true, + }} + selectedContactPointName={value} /> )} control={control} @@ -298,14 +289,6 @@ export const AmRoutesExpandedForm = ({ ); }; -function onCallFirst(receiver: AmRouteReceiver) { - if (receiver.grafanaAppReceiverType === SupportedPlugin.OnCall) { - return -1; - } else { - return 0; - } -} - const getStyles = (theme: GrafanaTheme2) => { const commonSpacing = theme.spacing(3.5); diff --git a/public/app/features/alerting/unified/components/notification-policies/Filters.tsx b/public/app/features/alerting/unified/components/notification-policies/Filters.tsx index 4989cf15a83..5fb6c57459e 100644 --- a/public/app/features/alerting/unified/components/notification-policies/Filters.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/Filters.tsx @@ -2,9 +2,9 @@ import { css } from '@emotion/css'; import { debounce, isEqual } from 'lodash'; import { useCallback, useEffect, useRef } from 'react'; -import { SelectableValue } from '@grafana/data'; -import { Button, Field, Icon, Input, Label, Select, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui'; -import { ObjectMatcher, Receiver, RouteWithID } from 'app/plugins/datasource/alertmanager/types'; +import { Button, Field, Icon, Input, Label, Stack, Text, Tooltip, useStyles2 } from '@grafana/ui'; +import { ContactPointSelector } from 'app/features/alerting/unified/components/notification-policies/ContactPointSelector'; +import { ObjectMatcher, RouteWithID } from 'app/plugins/datasource/alertmanager/types'; import { useURLSearchParams } from '../../hooks/useURLSearchParams'; import { matcherToObjectMatcher } from '../../utils/alertmanager'; @@ -15,14 +15,12 @@ import { } from '../../utils/matchers'; interface NotificationPoliciesFilterProps { - receivers: Receiver[]; onChangeMatchers: (labels: ObjectMatcher[]) => void; onChangeReceiver: (receiver: string | undefined) => void; matchingCount: number; } const NotificationPoliciesFilter = ({ - receivers, onChangeReceiver, onChangeMatchers, matchingCount, @@ -47,12 +45,9 @@ const NotificationPoliciesFilter = ({ if (searchInputRef.current) { searchInputRef.current.value = ''; } - setSearchParams({ contactPoint: undefined, queryString: undefined }); + setSearchParams({ contactPoint: '', queryString: undefined }); }, [setSearchParams]); - const receiverOptions: Array> = receivers.map(toOption); - const selectedContactPoint = receiverOptions.find((option) => option.value === contactPoint) ?? null; - const hasFilters = queryString || contactPoint; let inputValid = Boolean(queryString && queryString.length > 3); @@ -103,16 +98,17 @@ const NotificationPoliciesFilter = ({ /> - `Use: ${v}`} + onFocus={() => { + // Delay loading until we click on the name + setFocus(true); + }} + options={options} + isLoading={loading} + isClearable={true} + defaultOptions + value={value} + onChange={(v: SelectableValue) => { + props.onChange(v?.value ?? ''); + }} + onCreateOption={(v) => { + props.onChange(v); + }} + /> + ); + } + return ; + }} + + ); + }} + + ); +} diff --git a/public/swagger/SwaggerPage.tsx b/public/swagger/SwaggerPage.tsx new file mode 100644 index 00000000000..f32033cc206 --- /dev/null +++ b/public/swagger/SwaggerPage.tsx @@ -0,0 +1,105 @@ +import getDefaultMonacoLanguages from 'lib/monaco-languages'; +import { useState } from 'react'; +import { useAsync } from 'react-use'; +import SwaggerUI from 'swagger-ui-react'; + +import { createTheme, monacoLanguageRegistry, SelectableValue } from '@grafana/data'; +import { Stack, Select } from '@grafana/ui'; +import { setMonacoEnv } from 'app/core/monacoEnv'; +import { ThemeProvider } from 'app/core/utils/ConfigProvider'; + +import { NamespaceContext, WrappedPlugins } from './plugins'; + +export const Page = () => { + const theme = createTheme({ colors: { mode: 'light' } }); + const [url, setURL] = useState>(); + const urls = useAsync(async () => { + const v2 = { label: 'Grafana API (OpenAPI v2)', key: 'openapi2', value: 'public/api-merged.json' }; + const v3 = { label: 'Grafana API (OpenAPI v3)', key: 'openapi3', value: 'public/openapi3.json' }; + const urls: Array> = [v2, v3]; + + const rsp = await fetch('openapi/v3'); + const apis = await rsp.json(); + for (const [key, val] of Object.entries(apis.paths)) { + const parts = key.split('/'); + if (parts.length === 3) { + urls.push({ + key: `${parts[1]}-${parts[2]}`, + label: `${parts[1]}/${parts[2]}`, + value: val.serverRelativeURL.substring(1), // remove initial slash + }); + } + } + + let idx = 0; + const urlParams = new URLSearchParams(window.location.search); + const api = urlParams.get('api'); + if (api) { + urls.forEach((url, i) => { + if (url.key === api) { + idx = i; + } + }); + } + + monacoLanguageRegistry.setInit(getDefaultMonacoLanguages); + setMonacoEnv(); + + setURL(urls[idx]); // Remove to start at the generic landing page + return urls; + }); + + const namespace = useAsync(async () => { + const response = await fetch('api/frontend/settings'); + if (!response.ok) { + console.warn('No settings found'); + return ''; + } + const val = await response.json(); + return val.namespace; + }); + + return ( +
+ + +
+ + Grafana + - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx b/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx new file mode 100644 index 00000000000..ed654fa378d --- /dev/null +++ b/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx @@ -0,0 +1,172 @@ +import { useForm } from 'react-hook-form'; +import { useAsync } from 'react-use'; + +import { AppEvents, dateTime } from '@grafana/data'; +import { DataSourcePicker, getAppEvents, getDataSourceSrv } from '@grafana/runtime'; +import { DataQuery } from '@grafana/schema'; +import { Button, InlineSwitch, Modal, RadioButtonGroup, TextArea } from '@grafana/ui'; +import { Field } from '@grafana/ui/'; +import { Input } from '@grafana/ui/src/components/Input/Input'; +import { Trans, t } from 'app/core/internationalization'; +import { getQueryDisplayText } from 'app/core/utils/richHistory'; +import { useAddQueryTemplateMutation, useEditQueryTemplateMutation } from 'app/features/query-library'; +import { AddQueryTemplateCommand, EditQueryTemplateCommand } from 'app/features/query-library/types'; + +import { useDatasource } from '../QueryLibrary/utils/useDatasource'; + +import { QueryTemplateRow } from './QueryTemplatesTable/types'; + +type Props = { + onCancel: () => void; + onSave: (isSuccess: boolean) => void; + queryToAdd?: DataQuery; + templateData?: QueryTemplateRow; +}; + +export type QueryDetails = { + description: string; +}; + +const VisibilityOptions = [ + { value: 'Public', label: t('explore.query-library.public', 'Public') }, + { value: 'Private', label: t('explore.query-library.private', 'Private') }, +]; + +const getInstuctions = (isAdd: boolean) => { + return isAdd + ? t( + 'explore.query-template-modal.add-info', + `You're about to save this query. Once saved, you can easily access it in the Query Library tab for future use and reference.` + ) + : t( + 'explore.query-template-modal.edit-info', + `You're about to edit this query. Once saved, you can easily access it in the Query Library tab for future use and reference.` + ); +}; + +export const QueryTemplateForm = ({ onCancel, onSave, queryToAdd, templateData }: Props) => { + const { register, handleSubmit } = useForm({ + defaultValues: { + description: templateData?.description, + }, + }); + + const [addQueryTemplate] = useAddQueryTemplateMutation(); + const [editQueryTemplate] = useEditQueryTemplateMutation(); + + const datasource = useDatasource(queryToAdd?.datasource); + + // this is an array to support multi query templates sometime in the future + const queries = + queryToAdd !== undefined ? [queryToAdd] : templateData?.query !== undefined ? [templateData?.query] : []; + + const handleAddQueryTemplate = async (addQueryTemplateCommand: AddQueryTemplateCommand) => { + return addQueryTemplate(addQueryTemplateCommand) + .unwrap() + .then(() => { + getAppEvents().publish({ + type: AppEvents.alertSuccess.name, + payload: [ + t('explore.query-library.query-template-added', 'Query template successfully added to the library'), + ], + }); + return true; + }) + .catch(() => { + getAppEvents().publish({ + type: AppEvents.alertError.name, + payload: [ + t('explore.query-library.query-template-add-error', 'Error attempting to add this query to the library'), + ], + }); + return false; + }); + }; + + const handleEditQueryTemplate = async (editQueryTemplateCommand: EditQueryTemplateCommand) => { + return editQueryTemplate(editQueryTemplateCommand) + .unwrap() + .then(() => { + getAppEvents().publish({ + type: AppEvents.alertSuccess.name, + payload: [t('explore.query-library.query-template-edited', 'Query template successfully edited')], + }); + return true; + }) + .catch(() => { + getAppEvents().publish({ + type: AppEvents.alertError.name, + payload: [t('explore.query-library.query-template-edit-error', 'Error attempting to edit this query')], + }); + return false; + }); + }; + + const onSubmit = async (data: QueryDetails) => { + const timestamp = dateTime().toISOString(); + const temporaryDefaultTitle = + data.description || t('explore.query-library.default-description', 'Public', { timestamp: timestamp }); + + if (templateData?.uid) { + handleEditQueryTemplate({ uid: templateData.uid, partialSpec: { title: data.description } }).then((isSuccess) => { + onSave(isSuccess); + }); + } else if (queryToAdd) { + handleAddQueryTemplate({ title: temporaryDefaultTitle, targets: [queryToAdd] }).then((isSuccess) => { + onSave(isSuccess); + }); + } + }; + + const { value: queryText } = useAsync(async () => { + const promises = queries.map(async (query, i) => { + const datasource = await getDataSourceSrv().get(query.datasource); + return datasource?.getQueryDisplayText?.(query) || getQueryDisplayText(query); + }); + return Promise.all(promises); + }); + + return ( +
+

{getInstuctions(templateData === undefined)}

+ {queryText && + queryText.map((queryString, i) => ( + + + + ))} + {queryToAdd && ( + <> + + + + + + + + )} + + + + + + + + + + + + + ); +}; diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx index 8e78f64c295..e3ae8d14ce6 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/ActionsCell.tsx @@ -1,6 +1,7 @@ +import { useState } from 'react'; + import { reportInteraction, getAppEvents } from '@grafana/runtime'; -import { DataQuery } from '@grafana/schema'; -import { IconButton } from '@grafana/ui'; +import { IconButton, Modal } from '@grafana/ui'; import { notifyApp } from 'app/core/actions'; import { createSuccessNotification } from 'app/core/copy/appNotification'; import { t } from 'app/core/internationalization'; @@ -10,17 +11,20 @@ import { ShowConfirmModalEvent } from 'app/types/events'; import ExploreRunQueryButton from '../../ExploreRunQueryButton'; import { useQueriesDrawerContext } from '../../QueriesDrawer/QueriesDrawerContext'; +import { QueryTemplateForm } from '../QueryTemplateForm'; import { useQueryLibraryListStyles } from './styles'; +import { QueryTemplateRow } from './types'; interface ActionsCellProps { queryUid?: string; - query?: DataQuery; + queryTemplate: QueryTemplateRow; rootDatasourceUid?: string; } -function ActionsCell({ query, rootDatasourceUid, queryUid }: ActionsCellProps) { +function ActionsCell({ queryTemplate, rootDatasourceUid, queryUid }: ActionsCellProps) { const [deleteQueryTemplate] = useDeleteQueryTemplateMutation(); + const [editFormOpen, setEditFormOpen] = useState(false); const { setDrawerOpened } = useQueriesDrawerContext(); const styles = useQueryLibraryListStyles(); @@ -59,11 +63,34 @@ function ActionsCell({ query, rootDatasourceUid, queryUid }: ActionsCellProps) { } }} /> + { + setEditFormOpen(true); + }} + /> setDrawerOpened(false)} /> + setEditFormOpen(false)} + > + setEditFormOpen(false)} + templateData={queryTemplate} + onSave={() => { + setEditFormOpen(false); + }} + /> +
); } diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx index 376e7457dd8..c95398b0956 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesTable/index.tsx @@ -25,7 +25,7 @@ const columns: Array> = [ id: 'actions', header: '', cell: ({ row: { original } }) => ( - + ), }, ]; diff --git a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx index 8bfcde6e6e8..611558c0b19 100644 --- a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx @@ -5,7 +5,7 @@ import { DataQuery } from '@grafana/schema'; import { Button, Modal } from '@grafana/ui'; import { isQueryLibraryEnabled } from 'app/features/query-library'; -import { AddToLibraryForm } from '../QueryLibrary/AddToLibraryForm'; +import { QueryTemplateForm } from '../QueryLibrary/QueryTemplateForm'; type Props = { query: DataQuery; @@ -23,13 +23,13 @@ export const RichHistoryAddToLibrary = ({ query }: Props) => { {buttonLabel} setIsOpen(false)} > - setIsOpen(() => false)} - query={query} + queryToAdd={query} onSave={(isSuccess) => { if (isSuccess) { setIsOpen(false); diff --git a/public/app/features/query-library/api/factory.ts b/public/app/features/query-library/api/factory.ts index 09cc17f76f6..d67b8666aac 100644 --- a/public/app/features/query-library/api/factory.ts +++ b/public/app/features/query-library/api/factory.ts @@ -1,6 +1,6 @@ import { createApi } from '@reduxjs/toolkit/query/react'; -import { AddQueryTemplateCommand, DeleteQueryTemplateCommand, QueryTemplate } from '../types'; +import { AddQueryTemplateCommand, DeleteQueryTemplateCommand, EditQueryTemplateCommand, QueryTemplate } from '../types'; import { convertAddQueryTemplateCommandToDataQuerySpec, convertDataQueryResponseToQueryTemplates } from './mappers'; import { baseQuery } from './query'; @@ -28,6 +28,17 @@ export const queryLibraryApi = createApi({ }), invalidatesTags: ['QueryTemplatesList'], }), + editQueryTemplate: builder.mutation({ + query: (editQueryTemplateCommand) => ({ + url: `${editQueryTemplateCommand.uid}`, + method: 'PATCH', + headers: { + 'Content-Type': 'application/merge-patch+json', + }, + data: { spec: editQueryTemplateCommand.partialSpec }, + }), + invalidatesTags: ['QueryTemplatesList'], + }), }), reducerPath: 'queryLibrary', }); diff --git a/public/app/features/query-library/api/mappers.ts b/public/app/features/query-library/api/mappers.ts index 3df53b95795..6c0af6c8539 100644 --- a/public/app/features/query-library/api/mappers.ts +++ b/public/app/features/query-library/api/mappers.ts @@ -1,7 +1,7 @@ import { AddQueryTemplateCommand, QueryTemplate } from '../types'; import { API_VERSION, QueryTemplateKinds } from './query'; -import { CREATED_BY_KEY, DataQuerySpec, DataQuerySpecResponse, DataQueryTarget } from './types'; +import { CREATED_BY_KEY, DataQueryFullSpec, DataQuerySpecResponse, DataQueryTarget } from './types'; export const parseCreatedByValue = (value?: string) => { // https://github.com/grafana/grafana/blob/main/pkg/services/user/identity.go#L194 @@ -42,7 +42,7 @@ export const convertDataQueryResponseToQueryTemplates = (result: DataQuerySpecRe export const convertAddQueryTemplateCommandToDataQuerySpec = ( addQueryTemplateCommand: AddQueryTemplateCommand -): DataQuerySpec => { +): DataQueryFullSpec => { const { title, targets } = addQueryTemplateCommand; return { apiVersion: API_VERSION, diff --git a/public/app/features/query-library/api/query.ts b/public/app/features/query-library/api/query.ts index 2d08c1736ae..eb730daf594 100644 --- a/public/app/features/query-library/api/query.ts +++ b/public/app/features/query-library/api/query.ts @@ -28,6 +28,7 @@ export const BASE_URL = `/apis/${API_VERSION}/namespaces/${config.namespace}/que // URL is optional for these requests interface QueryLibraryBackendRequest extends Pick { url?: string; + headers?: { [key: string]: string }; } /** @@ -42,6 +43,7 @@ export const baseQuery: BaseQueryFn; + export type DataQuerySpecResponse = { apiVersion: string; - items: DataQuerySpec[]; + items: DataQueryFullSpec[]; }; export const CREATED_BY_KEY = 'grafana.app/createdBy'; diff --git a/public/app/features/query-library/index.ts b/public/app/features/query-library/index.ts index 513b7ca5bb8..8c5bd02a098 100644 --- a/public/app/features/query-library/index.ts +++ b/public/app/features/query-library/index.ts @@ -12,8 +12,12 @@ import { config } from '@grafana/runtime'; import { queryLibraryApi } from './api/factory'; import { mockData } from './api/mocks'; -export const { useAllQueryTemplatesQuery, useAddQueryTemplateMutation, useDeleteQueryTemplateMutation } = - queryLibraryApi; +export const { + useAllQueryTemplatesQuery, + useAddQueryTemplateMutation, + useDeleteQueryTemplateMutation, + useEditQueryTemplateMutation, +} = queryLibraryApi; export function isQueryLibraryEnabled() { return config.featureToggles.queryLibrary; diff --git a/public/app/features/query-library/types.ts b/public/app/features/query-library/types.ts index 66a92b085a2..91e050b2774 100644 --- a/public/app/features/query-library/types.ts +++ b/public/app/features/query-library/types.ts @@ -1,5 +1,7 @@ import { DataQuery } from '@grafana/schema'; +import { DataQueryPartialSpec } from './api/types'; + export type QueryTemplate = { uid: string; title: string; @@ -13,6 +15,11 @@ export type AddQueryTemplateCommand = { targets: DataQuery[]; }; +export type EditQueryTemplateCommand = { + uid: string; + partialSpec: DataQueryPartialSpec; +}; + export type DeleteQueryTemplateCommand = { uid: string; }; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 685a46c6580..d0d99aaffcb 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -702,16 +702,6 @@ }, "explore": { "add-to-dashboard": "Add to dashboard", - "add-to-library-modal": { - "auto-star": "Auto-star this query to add it to your starred list in the Query Library.", - "data-source-name": "Data source name", - "data-source-type": "Data source type", - "description": "Description", - "info": "You're about to save this query. Once saved, you can easily access it in the Query Library tab for future use and reference.", - "query": "Query", - "title": "Add query to Query Library", - "visibility": "Visibility" - }, "logs": { "maximum-pinned-logs": "Maximum of {{PINNED_LOGS_LIMIT}} pinned logs reached. Unpin a log to add another.", "no-logs-found": "No logs found.", @@ -719,6 +709,7 @@ "stop-scan": "Stop scan" }, "query-library": { + "add-edit-description": "Add/edit description", "cancel": "Cancel", "default-description": "Public", "delete-query": "Delete query", @@ -727,10 +718,26 @@ "private": "Private", "public": "Public", "query-deleted": "Query deleted", + "query-template-add-error": "Error attempting to add this query to the library", "query-template-added": "Query template successfully added to the library", - "query-template-error": "Error attempting to add this query to the library", + "query-template-edit-error": "Error attempting to edit this query", + "query-template-edited": "Query template successfully edited", "save": "Save" }, + "query-template-modal": { + "add-info": "You're about to save this query. Once saved, you can easily access it in the Query Library tab for future use and reference.", + "add-title": "Add query to Query Library", + "auto-star": "Auto-star this query to add it to your starred list in the Query Library.", + "data-source-name": "Data source name", + "description": "Description", + "edit-info": "You're about to edit this query. Once saved, you can easily access it in the Query Library tab for future use and reference.", + "edit-title": "Edit query", + "query": "Query", + "visibility": "Visibility" + }, + "query-template-modall": { + "data-source-type": "Data source type" + }, "rich-history": { "close-tooltip": "Close query history", "datasource-a-z": "Data source A-Z", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 01c8396aca1..3f31878d512 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -702,16 +702,6 @@ }, "explore": { "add-to-dashboard": "Åđđ ŧő đäşĥþőäřđ", - "add-to-library-modal": { - "auto-star": "Åūŧő-şŧäř ŧĥįş qūęřy ŧő äđđ įŧ ŧő yőūř şŧäřřęđ ľįşŧ įʼn ŧĥę Qūęřy Ŀįþřäřy.", - "data-source-name": "Đäŧä şőūřčę ʼnämę", - "data-source-type": "Đäŧä şőūřčę ŧypę", - "description": "Đęşčřįpŧįőʼn", - "info": "Ÿőū'řę äþőūŧ ŧő şävę ŧĥįş qūęřy. Øʼnčę şävęđ, yőū čäʼn ęäşįľy äččęşş įŧ įʼn ŧĥę Qūęřy Ŀįþřäřy ŧäþ ƒőř ƒūŧūřę ūşę äʼnđ řęƒęřęʼnčę.", - "query": "Qūęřy", - "title": "Åđđ qūęřy ŧő Qūęřy Ŀįþřäřy", - "visibility": "Vįşįþįľįŧy" - }, "logs": { "maximum-pinned-logs": "Mäχįmūm őƒ {{PINNED_LOGS_LIMIT}} pįʼnʼnęđ ľőģş řęäčĥęđ. Ůʼnpįʼn ä ľőģ ŧő äđđ äʼnőŧĥęř.", "no-logs-found": "Ńő ľőģş ƒőūʼnđ.", @@ -719,6 +709,7 @@ "stop-scan": "Ŝŧőp şčäʼn" }, "query-library": { + "add-edit-description": "Åđđ/ęđįŧ đęşčřįpŧįőʼn", "cancel": "Cäʼnčęľ", "default-description": "Pūþľįč", "delete-query": "Đęľęŧę qūęřy", @@ -727,10 +718,26 @@ "private": "Přįväŧę", "public": "Pūþľįč", "query-deleted": "Qūęřy đęľęŧęđ", + "query-template-add-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő äđđ ŧĥįş qūęřy ŧő ŧĥę ľįþřäřy", "query-template-added": "Qūęřy ŧęmpľäŧę şūččęşşƒūľľy äđđęđ ŧő ŧĥę ľįþřäřy", - "query-template-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő äđđ ŧĥįş qūęřy ŧő ŧĥę ľįþřäřy", + "query-template-edit-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő ęđįŧ ŧĥįş qūęřy", + "query-template-edited": "Qūęřy ŧęmpľäŧę şūččęşşƒūľľy ęđįŧęđ", "save": "Ŝävę" }, + "query-template-modal": { + "add-info": "Ÿőū'řę äþőūŧ ŧő şävę ŧĥįş qūęřy. Øʼnčę şävęđ, yőū čäʼn ęäşįľy äččęşş įŧ įʼn ŧĥę Qūęřy Ŀįþřäřy ŧäþ ƒőř ƒūŧūřę ūşę äʼnđ řęƒęřęʼnčę.", + "add-title": "Åđđ qūęřy ŧő Qūęřy Ŀįþřäřy", + "auto-star": "Åūŧő-şŧäř ŧĥįş qūęřy ŧő äđđ įŧ ŧő yőūř şŧäřřęđ ľįşŧ įʼn ŧĥę Qūęřy Ŀįþřäřy.", + "data-source-name": "Đäŧä şőūřčę ʼnämę", + "description": "Đęşčřįpŧįőʼn", + "edit-info": "Ÿőū'řę äþőūŧ ŧő ęđįŧ ŧĥįş qūęřy. Øʼnčę şävęđ, yőū čäʼn ęäşįľy äččęşş įŧ įʼn ŧĥę Qūęřy Ŀįþřäřy ŧäþ ƒőř ƒūŧūřę ūşę äʼnđ řęƒęřęʼnčę.", + "edit-title": "Ēđįŧ qūęřy", + "query": "Qūęřy", + "visibility": "Vįşįþįľįŧy" + }, + "query-template-modall": { + "data-source-type": "Đäŧä şőūřčę ŧypę" + }, "rich-history": { "close-tooltip": "Cľőşę qūęřy ĥįşŧőřy", "datasource-a-z": "Đäŧä şőūřčę Å-Ż", From 8a971431201953b1e38c7f662e0120a44751e3b7 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Wed, 14 Aug 2024 13:15:47 -0300 Subject: [PATCH 076/229] ShareDrawer: Share snapshot panel (#90678) --- .../scene/PanelMenuBehavior.tsx | 18 ++++++++++ .../scene/keyboardShortcuts.ts | 16 +++++++++ .../share-snapshot/CreateSnapshot.tsx | 34 ++++++++++++++----- .../share-snapshot/ShareSnapshot.tsx | 3 +- public/locales/en-US/grafana.json | 11 ++++-- public/locales/pseudo-LOCALE/grafana.json | 11 ++++-- 6 files changed, 78 insertions(+), 15 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx index c649726905f..9a736dc2936 100644 --- a/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx +++ b/public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx @@ -13,6 +13,7 @@ import { LocalValueVariable, sceneGraph, SceneGridRow, VizPanel, VizPanelMenu } import { DataQuery, OptionsWithLegend } from '@grafana/schema'; import appEvents from 'app/core/app_events'; import { t } from 'app/core/internationalization'; +import { contextSrv } from 'app/core/services/context_srv'; import { scenesPanelToRuleFormValues } from 'app/features/alerting/unified/utils/rule-form'; import { shareDashboardType } from 'app/features/dashboard/components/ShareModal/utils'; import { InspectTab } from 'app/features/inspector/types'; @@ -21,6 +22,7 @@ import { createExtensionSubMenu } from 'app/features/plugins/extensions/utils'; import { addDataTrailPanelAction } from 'app/features/trails/Integrations/dashboardIntegration'; import { ShowConfirmModalEvent } from 'app/types/events'; +import { ShareSnapshot } from '../sharing/ShareButton/share-snapshot/ShareSnapshot'; import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer'; import { ShareModal } from '../sharing/ShareModal'; import { SharePanelEmbedTab } from '../sharing/SharePanelEmbedTab'; @@ -109,6 +111,22 @@ export function panelMenuBehavior(menu: VizPanelMenu, isRepeat = false) { }, }); + if (contextSrv.isSignedIn && config.snapshotEnabled && dashboard.canEditDashboard()) { + subMenu.push({ + text: t('share-panel.menu.share-snapshot-title', 'Share snapshot'), + iconClassName: 'camera', + shortcut: 'p s', + onClick: () => { + const drawer = new ShareDrawer({ + title: t('share-panel.drawer.share-snapshot-title', 'Share snapshot'), + body: new ShareSnapshot({ dashboardRef: dashboard.getRef(), panelRef: panel.getRef() }), + }); + + dashboard.showModal(drawer); + }, + }); + } + items.push({ type: 'submenu', text: t('panel.header-menu.share', 'Share'), diff --git a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts index 1991aef027b..90d2baa7093 100644 --- a/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts +++ b/public/app/features/dashboard-scene/scene/keyboardShortcuts.ts @@ -4,7 +4,9 @@ import { sceneGraph, VizPanel } from '@grafana/scenes'; import appEvents from 'app/core/app_events'; import { t } from 'app/core/internationalization'; import { KeybindingSet } from 'app/core/services/KeybindingSet'; +import { contextSrv } from 'app/core/services/context_srv'; +import { ShareSnapshot } from '../sharing/ShareButton/share-snapshot/ShareSnapshot'; import { ShareDrawer } from '../sharing/ShareDrawer/ShareDrawer'; import { ShareModal } from '../sharing/ShareModal'; import { SharePanelEmbedTab } from '../sharing/SharePanelEmbedTab'; @@ -72,6 +74,20 @@ export function setupKeyboardShortcuts(scene: DashboardScene) { scene.showModal(drawer); }), }); + + if (contextSrv.isSignedIn && config.snapshotEnabled && scene.canEditDashboard()) { + keybindings.addBinding({ + key: 'p s', + onTrigger: withFocusedPanel(scene, async (vizPanel: VizPanel) => { + const drawer = new ShareDrawer({ + title: t('share-panel.drawer.share-snapshot-title', 'Share snapshot'), + body: new ShareSnapshot({ dashboardRef: scene.getRef(), panelRef: vizPanel.getRef() }), + }); + + scene.showModal(drawer); + }), + }); + } } else { keybindings.addBinding({ key: 'p s', diff --git a/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/CreateSnapshot.tsx b/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/CreateSnapshot.tsx index 73aaf435fde..85cf3b3e0c9 100644 --- a/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/CreateSnapshot.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/CreateSnapshot.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/css'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; +import { SceneObjectRef, VizPanel } from '@grafana/scenes'; import { Alert, Button, @@ -20,7 +21,10 @@ import { Trans } from 'app/core/internationalization'; import { SnapshotSharingOptions } from '../../../../dashboard/services/SnapshotSrv'; import { getExpireOptions } from '../../ShareSnapshotTab'; -const SNAPSHOT_URL = 'https://grafana.com/docs/grafana/latest/dashboards/share-dashboards-panels/#publish-a-snapshot'; +const DASHBOARD_SNAPSHOT_URL = + 'https://grafana.com/docs/grafana/latest/dashboards/share-dashboards-panels/#publish-a-snapshot'; +const PANEL_SNAPSHOT_URL = + 'https://grafana.com/docs/grafana/latest/dashboards/share-dashboards-panels/#publish-a-snapshot-1'; interface Props { isLoading: boolean; @@ -31,6 +35,7 @@ interface Props { onCreateClick: (isExternal?: boolean) => void; onNameChange: (v: string) => void; onExpireChange: (v: number) => void; + panelRef?: SceneObjectRef; } export function CreateSnapshot({ name, @@ -41,6 +46,7 @@ export function CreateSnapshot({ onCancelClick, onCreateClick, isLoading, + panelRef, }: Props) { const styles = useStyles2(getStyles); @@ -49,18 +55,30 @@ export function CreateSnapshot({ - - A Grafana dashboard snapshot publicly shares a dashboard while removing sensitive data such as queries and - panel links, leaving only visible metrics and series names. Anyone with the link can access the snapshot. - + {panelRef ? ( + + A Grafana panel snapshot publicly shares a panel while removing sensitive data such as queries and panel + links, leaving only visible metrics and series names. Anyone with the link can access the snapshot. + + ) : ( + + A Grafana dashboard snapshot publicly shares a dashboard while removing sensitive data such as queries + and panel links, leaving only visible metrics and series names. Anyone with the link can access the + snapshot. + + )} - - - onNameChange(e.target.value)} /> + + onNameChange(e.currentTarget.value)} /> diff --git a/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/ShareSnapshot.tsx b/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/ShareSnapshot.tsx index d225a75b1e3..60ba8c30992 100644 --- a/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/ShareSnapshot.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareButton/share-snapshot/ShareSnapshot.tsx @@ -23,7 +23,7 @@ function ShareSnapshotRenderer({ model }: SceneComponentProps) { const [showDeletedAlert, setShowDeletedAlert] = useState(false); const [step, setStep] = useState(1); - const { snapshotName, snapshotSharingOptions, selectedExpireOption, dashboardRef } = model.useState(); + const { snapshotName, snapshotSharingOptions, selectedExpireOption, dashboardRef, panelRef } = model.useState(); const [snapshotResult, createSnapshot] = useAsyncFn(async (external = false) => { const response = await model.onSnapshotCreate(external); @@ -76,6 +76,7 @@ function ShareSnapshotRenderer({ model }: SceneComponentProps) { onExpireChange={model.onExpireChange} onCreateClick={createSnapshot} isLoading={snapshotResult.loading} + panelRef={panelRef} /> )} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index d0d99aaffcb..22ea985ed88 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2130,11 +2130,13 @@ "share-panel": { "drawer": { "share-embed-title": "Share embed", - "share-link-title": "Link settings" + "share-link-title": "Link settings", + "share-snapshot-title": "Share snapshot" }, "menu": { "share-embed-title": "Share embed", - "share-link-title": "Share link" + "share-link-title": "Share link", + "share-snapshot-title": "Share snapshot" } }, "share-playlist": { @@ -2215,12 +2217,15 @@ "info-alert": "A Grafana dashboard snapshot publicly shares a dashboard while removing sensitive data such as queries and panel links, leaving only visible metrics and series names. Anyone with the link can access the snapshot.", "learn-more-button": "Learn more", "local-button": "Publish snapshot", - "name-label": "Snapshot name*", + "name-label": "Snapshot name", "new-snapshot-button": "New snapshot", "success-creation": "Your snapshot has been created", "success-delete": "Your snapshot has been deleted", "view-all-button": "View all snapshots" }, + "share-panel": { + "info-alert": "A Grafana panel snapshot publicly shares a panel while removing sensitive data such as queries and panel links, leaving only visible metrics and series names. Anyone with the link can access the snapshot." + }, "url-column-header": "Snapshot url", "view-button": "View" }, diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 3f31878d512..63d19c67f5a 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -2130,11 +2130,13 @@ "share-panel": { "drawer": { "share-embed-title": "Ŝĥäřę ęmþęđ", - "share-link-title": "Ŀįʼnĸ şęŧŧįʼnģş" + "share-link-title": "Ŀįʼnĸ şęŧŧįʼnģş", + "share-snapshot-title": "Ŝĥäřę şʼnäpşĥőŧ" }, "menu": { "share-embed-title": "Ŝĥäřę ęmþęđ", - "share-link-title": "Ŝĥäřę ľįʼnĸ" + "share-link-title": "Ŝĥäřę ľįʼnĸ", + "share-snapshot-title": "Ŝĥäřę şʼnäpşĥőŧ" } }, "share-playlist": { @@ -2215,12 +2217,15 @@ "info-alert": "Å Ğřäƒäʼnä đäşĥþőäřđ şʼnäpşĥőŧ pūþľįčľy şĥäřęş ä đäşĥþőäřđ ŵĥįľę řęmővįʼnģ şęʼnşįŧįvę đäŧä şūčĥ äş qūęřįęş äʼnđ päʼnęľ ľįʼnĸş, ľęävįʼnģ őʼnľy vįşįþľę męŧřįčş äʼnđ şęřįęş ʼnämęş. Åʼnyőʼnę ŵįŧĥ ŧĥę ľįʼnĸ čäʼn äččęşş ŧĥę şʼnäpşĥőŧ.", "learn-more-button": "Ŀęäřʼn mőřę", "local-button": "Pūþľįşĥ şʼnäpşĥőŧ", - "name-label": "Ŝʼnäpşĥőŧ ʼnämę*", + "name-label": "Ŝʼnäpşĥőŧ ʼnämę", "new-snapshot-button": "Ńęŵ şʼnäpşĥőŧ", "success-creation": "Ÿőūř şʼnäpşĥőŧ ĥäş þęęʼn čřęäŧęđ", "success-delete": "Ÿőūř şʼnäpşĥőŧ ĥäş þęęʼn đęľęŧęđ", "view-all-button": "Vįęŵ äľľ şʼnäpşĥőŧş" }, + "share-panel": { + "info-alert": "Å Ğřäƒäʼnä päʼnęľ şʼnäpşĥőŧ pūþľįčľy şĥäřęş ä päʼnęľ ŵĥįľę řęmővįʼnģ şęʼnşįŧįvę đäŧä şūčĥ äş qūęřįęş äʼnđ päʼnęľ ľįʼnĸş, ľęävįʼnģ őʼnľy vįşįþľę męŧřįčş äʼnđ şęřįęş ʼnämęş. Åʼnyőʼnę ŵįŧĥ ŧĥę ľįʼnĸ čäʼn äččęşş ŧĥę şʼnäpşĥőŧ." + }, "url-column-header": "Ŝʼnäpşĥőŧ ūřľ", "view-button": "Vįęŵ" }, From 98a74d844e831f4008d5d1e0a218d44b83ca1b1b Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:22:55 +0200 Subject: [PATCH 077/229] Add `grafana_state_reason` section in State of alerts (#91562) * Add `grafana_state_reason` section in State of alerts * Minor edit for clarification * Mention `Paused/RuleDeleted/Updated` states --- .../create-grafana-managed-rule.md | 4 +- .../alert-rule-evaluation/state-and-health.md | 43 +++++++++++++------ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md index 9ab472e172a..65bd708d356 100644 --- a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md +++ b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md @@ -256,7 +256,7 @@ You can configure the alert instance state when its evaluation returns no data: | No Data configuration | Description | | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | No Data | The default option. Sets alert instance state to `No data`.
The alert rule also creates a new alert instance `DatasourceNoData` with the name and UID of the alert rule, and UID of the datasource that returned no data as labels. | -| Alerting | Sets alert instance state to `Alerting`. It waits until the [pending period](ref:pending-period) has finished. | +| Alerting | Sets alert instance state to `Alerting`. It transitions from `Pending` to `Alerting` after the [pending period](ref:pending-period) has finished. | | Normal | Sets alert instance state to `Normal`. | | Keep Last State | Maintains the alert instance in its last state. Useful for mitigating temporary issues, refer to [Keep last state](ref:keep-last-state). | @@ -265,7 +265,7 @@ You can also configure the alert instance state when its evaluation returns an e | Error configuration | Description | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Error | The default option. Sets alert instance state to `Error`.
The alert rule also creates a new alert instance `DatasourceError` with the name and UID of the alert rule, and UID of the datasource that returned no data as labels. | -| Alerting | Sets alert instance state to `Alerting`. It waits until the [pending period](ref:pending-period) has finished. | +| Alerting | Sets alert instance state to `Alerting`. It transitions from `Pending` to `Alerting` after the [pending period](ref:pending-period) has finished. | | Normal | Sets alert instance state to `Normal`. | | Keep Last State | Maintains the alert instance in its last state. Useful for mitigating temporary issues, refer to [Keep last state](ref:keep-last-state). | diff --git a/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md b/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md index b40aca49dc4..6b516e80a92 100644 --- a/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md +++ b/docs/sources/alerting/fundamentals/alert-rule-evaluation/state-and-health.md @@ -44,13 +44,13 @@ There are three key components that help you understand how your alerts behave d An alert instance can be in either of the following states: -| State | Description | -| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **Normal** | The state of an alert when the condition (threshold) is not met. | -| **Pending** | The state of an alert that has breached the threshold but for less than the [pending period](ref:pending-period). | -| **Alerting** | The state of an alert that has breached the threshold for longer than the [pending period](ref:pending-period). | -| **NoData** | The state of an alert whose query returns no data or all values are null. You can [change the default behavior](/docs/grafana/latest/alerting/alerting-rules/create-grafana-managed-rule/#configure-no-data-and-error-handling). | -| **Error** | The state of an alert when an error or timeout occurred evaluating the alert rule. You can [change the default behavior](/docs/grafana/latest/alerting/alerting-rules/create-grafana-managed-rule/#configure-no-data-and-error-handling). | +| State | Description | +| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Normal** | The state of an alert when the condition (threshold) is not met. | +| **Pending** | The state of an alert that has breached the threshold but for less than the [pending period](ref:pending-period). | +| **Alerting** | The state of an alert that has breached the threshold for longer than the [pending period](ref:pending-period). | +| **NoData** | The state of an alert whose query returns no data or all values are null. You can [change the default behavior of the no data state](#modify-the-no-data-and-error-state). | +| **Error** | The state of an alert when an error or timeout occurred evaluating the alert rule. You can [change the default behavior of the error state](#modify-the-no-data-and-error-state). | {{< figure src="/media/docs/alerting/alert-instance-states-v3.png" caption="Alert instance state diagram" alt="A diagram of the distinct alert instance states and transitions." max-width="750px" >}} @@ -64,18 +64,37 @@ Alert instances will be routed for [notifications](ref:notifications) when they An alert instance is considered stale if its dimension or series has disappeared from the query results entirely for two evaluation intervals. -Stale alert instances that are in the **Alerting**, **NoData**, or **Error** states transition to the **Normal** state as **Resolved**, and include the `grafana_state_reason` annotation with the value **MissingSeries**. They are routed for notifications like other resolved alert instances. +Stale alert instances that are in the **Alerting**, **NoData**, or **Error** states transition to the **Normal** state as **Resolved**. Once transitioned, these resolved alert instances are routed for notifications like other resolved alerts. -### Keep last state +### Modify the no data and error state -The "Keep Last State" option helps mitigate temporary data source issues, preventing alerts from unintentionally firing, resolving, and re-firing. - -In [Configure no data and error handling,](ref:no-data-and-error-handling) you can decide to keep the last state of the alert instance when a `NoData` and/or `Error` state is encountered. Just like normal evaluation, the alert instance transitions from `Pending` to `Alerting` after the pending period has elapsed. +In [Configure no data and error handling](ref:no-data-and-error-handling), you can change the default behaviour when the evaluation returns no data or an error. You can set the alert instance state to `Alerting`, `Normal`, or keep the last state. {{< figure src="/media/docs/alerting/alert-rule-configure-no-data-and-error.png" alt="A screenshot of the `Configure no data and error handling` option in Grafana Alerting." max-width="500px" >}} +#### Keep last state + +The "Keep Last State" option helps mitigate temporary data source issues, preventing alerts from unintentionally firing, resolving, and re-firing. + However, in situations where strict monitoring is critical, relying solely on the "Keep Last State" option may not be appropriate. Instead, consider using an alternative or implementing additional alert rules to ensure that issues with prolonged data source disruptions are detected. +### `grafana_state_reason` annotation + +Occasionally, an alert instance may be in a state that isn't immediately clear to everyone. For example: + +- Stale alert instances in the `Alerting` state transition to the `Normal` state when the series disappear. +- If "no data" handling is configured to transition to a state other than `NoData`. +- If "error" handling is configured to transition to a state other than `Error`. +- If the alert rule is deleted, paused, or updated in some cases, the alert instance also transitions to the `Normal` state. + +In these situations, the evaluation state may differ from the alert state, and it might be necessary to understand the reason for being in that state when receiving the notification. + +The `grafana_state_reason` annotation is included in these situations, providing the reason in the notifications that explain why the alert instance transitioned to its current state. For example: + +- Stale alert instances in the `Normal` state include the `grafana_state_reason` annotation with the value **MissingSeries**. +- If "no data" or "error" handling transitions to the `Normal` state, the `grafana_state_reason` annotation is included with the value **NoData** or **Error**, respectively. +- If the alert rule is deleted or paused, the `grafana_state_reason` is set to **Paused** or **RuleDeleted**. For some updates, it is set to **Updated**. + ### Special alerts for `NoData` and `Error` When evaluation of an alert rule produces state `NoData` or `Error`, Grafana Alerting generates a new alert instance that have the following additional labels: From 648005e0bb89b8a2bf4a4c587a0b27049d964180 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:47:57 -0300 Subject: [PATCH 078/229] Release: update changelog for 10.4.7 (#91908) * Update changelog * fix changelog --------- Co-authored-by: github-actions[bot] Co-authored-by: Diego Augusto Molina --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bec6b577d4e..253d9b4db2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ + + +# 10.4.7 (2024-08-14) + +### Bug fixes + +- **Swagger:** Fixed CVE-2024-6837. + + # 11.1.3 (2024-07-26) From 3087eae71d29a8152c643bb4e011233286d91dd0 Mon Sep 17 00:00:00 2001 From: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:53:09 -0400 Subject: [PATCH 079/229] Docs: Add max logo size (#91800) --- docs/sources/dashboards/create-reports/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/dashboards/create-reports/index.md b/docs/sources/dashboards/create-reports/index.md index 425070ce016..ae06d3becc9 100644 --- a/docs/sources/dashboards/create-reports/index.md +++ b/docs/sources/dashboards/create-reports/index.md @@ -335,11 +335,11 @@ You can customize the branding options. Report branding: -- **Company logo:** Company logo displayed in the report PDF. It can be configured by specifying a URL, or by uploading a file. Defaults to the Grafana logo. +- **Company logo:** Company logo displayed in the report PDF. It can be configured by specifying a URL, or by uploading a file. The maximum file size is 16 MB. Defaults to the Grafana logo. Email branding: -- **Company logo:** Company logo displayed in the report email. It can be configured by specifying a URL, or by uploading a file. Defaults to the Grafana logo. +- **Company logo:** Company logo displayed in the report email. It can be configured by specifying a URL, or by uploading a file. The maximum file size is 16 MB. Defaults to the Grafana logo. - **Email footer:** Toggle to enable the report email footer. Select **Sent by** or **None**. - **Footer link text:** Text of the link in the report email footer. Defaults to `Grafana`. - **Footer link URL:** Link of the report email footer. From 8c4da28d7c6bcb8c2376361d98d6fedbca349cd1 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:47:21 -0300 Subject: [PATCH 080/229] Release: update changelog for 11.0.3 (#91923) * Update changelog * fix changelog --------- Co-authored-by: github-actions[bot] Co-authored-by: Diego Augusto Molina --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 253d9b4db2b..85c49035b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ + + +# 11.0.3 (2024-08-14) + +### Bug fixes + +- **Swagger:** Fixed CVE-2024-6837. + + # 10.4.7 (2024-08-14) From 40144eb3c8ff4729e3769dbfd54e94a3947825bd Mon Sep 17 00:00:00 2001 From: Kyle Cunningham Date: Wed, 14 Aug 2024 13:49:28 -0500 Subject: [PATCH 081/229] Table: Fix edge case where text wrapping crashes on undefined header widths (#91850) Make sure we don't read from header groups if it's undefined --- packages/grafana-ui/src/components/Table/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Table/utils.ts b/packages/grafana-ui/src/components/Table/utils.ts index 940734cbd2f..bbd6c66cc13 100644 --- a/packages/grafana-ui/src/components/Table/utils.ts +++ b/packages/grafana-ui/src/components/Table/utils.ts @@ -645,7 +645,7 @@ export function guessTextBoundingBox( lineHeight: number, defaultRowHeight: number ) { - const width = Number(headerGroup.width ?? 300); + const width = Number(headerGroup?.width ?? 300); const LINE_SCALE_FACTOR = 1.17; const LOW_LINE_PAD = 42; From f158d52ae459c6f763a75eea78788d6cbcc17e6c Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Wed, 14 Aug 2024 15:28:57 -0400 Subject: [PATCH 082/229] Aggregator: Rename DataServiceType to QueryServiceType (#91929) --- pkg/aggregator/apis/aggregation/types.go | 4 ++-- pkg/aggregator/apis/aggregation/v0alpha1/types.go | 4 ++-- .../apis/aggregation/v0alpha1/zz_generated.openapi.go | 4 ++-- pkg/aggregator/apiserver/handler_plugin.go | 2 +- pkg/aggregator/apiserver/handler_plugin_test.go | 2 +- pkg/aggregator/examples/prometheus.yml | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/aggregator/apis/aggregation/types.go b/pkg/aggregator/apis/aggregation/types.go index cb7509cbd9d..67a3654f774 100644 --- a/pkg/aggregator/apis/aggregation/types.go +++ b/pkg/aggregator/apis/aggregation/types.go @@ -69,8 +69,8 @@ const ( AdmissionControlServiceType ServiceType = "admission" // ConversionServiceType maps to pluginv2.ResourceConversion ConversionServiceType ServiceType = "conversion" - // DataSourceServiceType maps to pluginv2.Data - DataServiceType ServiceType = "data" + // QueryServiceType maps to pluginv2.Data + QueryServiceType ServiceType = "query" // StreamServiceType maps to pluginv2.Stream StreamServiceType ServiceType = "stream" // RouteServiceType maps pluginv2.Resource diff --git a/pkg/aggregator/apis/aggregation/v0alpha1/types.go b/pkg/aggregator/apis/aggregation/v0alpha1/types.go index 0f48260728d..2f9c6439daa 100644 --- a/pkg/aggregator/apis/aggregation/v0alpha1/types.go +++ b/pkg/aggregator/apis/aggregation/v0alpha1/types.go @@ -69,8 +69,8 @@ const ( AdmissionControlServiceType ServiceType = "admission" // ConversionServiceType maps to pluginv2.ResourceConversion ConversionServiceType ServiceType = "conversion" - // DataSourceServiceType maps to pluginv2.Data - DataServiceType ServiceType = "data" + // QueryServiceType maps to pluginv2.Data + QueryServiceType ServiceType = "query" // StreamServiceType maps to pluginv2.Stream StreamServiceType ServiceType = "stream" // RouteServiceType maps pluginv2.Resource diff --git a/pkg/aggregator/apis/aggregation/v0alpha1/zz_generated.openapi.go b/pkg/aggregator/apis/aggregation/v0alpha1/zz_generated.openapi.go index da85a4c88d9..16a31a94a21 100644 --- a/pkg/aggregator/apis/aggregation/v0alpha1/zz_generated.openapi.go +++ b/pkg/aggregator/apis/aggregation/v0alpha1/zz_generated.openapi.go @@ -326,11 +326,11 @@ func schema_aggregator_apis_aggregation_v0alpha1_Service(ref common.ReferenceCal Properties: map[string]spec.Schema{ "type": { SchemaProps: spec.SchemaProps{ - Description: "Type is the type of service to proxy.\n\nPossible enum values:\n - `\"admission\"` maps to pluginv2.AdmissionControl\n - `\"conversion\"` maps to pluginv2.ResourceConversion\n - `\"data\"` DataSourceServiceType maps to pluginv2.Data\n - `\"datsource-proxy\"` is a reverse proxy for making requests directly to the HTTP URL specified in datasource instance settings.\n - `\"route\"` maps pluginv2.Resource\n - `\"stream\"` maps to pluginv2.Stream", + Description: "Type is the type of service to proxy.\n\nPossible enum values:\n - `\"admission\"` maps to pluginv2.AdmissionControl\n - `\"conversion\"` maps to pluginv2.ResourceConversion\n - `\"datsource-proxy\"` is a reverse proxy for making requests directly to the HTTP URL specified in datasource instance settings.\n - `\"query\"` maps to pluginv2.Data\n - `\"route\"` maps pluginv2.Resource\n - `\"stream\"` maps to pluginv2.Stream", Default: "", Type: []string{"string"}, Format: "", - Enum: []interface{}{"admission", "conversion", "data", "datsource-proxy", "route", "stream"}, + Enum: []interface{}{"admission", "conversion", "datsource-proxy", "query", "route", "stream"}, }, }, "method": { diff --git a/pkg/aggregator/apiserver/handler_plugin.go b/pkg/aggregator/apiserver/handler_plugin.go index f9f47a9aa3b..f375ab523a0 100644 --- a/pkg/aggregator/apiserver/handler_plugin.go +++ b/pkg/aggregator/apiserver/handler_plugin.go @@ -43,7 +43,7 @@ func newPluginHandler( for _, service := range dataplaneService.Spec.Services { switch service.Type { - case aggregationv0alpha1.DataServiceType: + case aggregationv0alpha1.QueryServiceType: proxyPath := fmt.Sprintf("/apis/%s/%s/namespaces/{namespace}/connections/{uid}/query", dataplaneService.Spec.Group, dataplaneService.Spec.Version) mux.Handle(proxyPath, h.QueryDataHandler()) case aggregationv0alpha1.StreamServiceType: diff --git a/pkg/aggregator/apiserver/handler_plugin_test.go b/pkg/aggregator/apiserver/handler_plugin_test.go index 125d4dacfc2..c6110b64b6e 100644 --- a/pkg/aggregator/apiserver/handler_plugin_test.go +++ b/pkg/aggregator/apiserver/handler_plugin_test.go @@ -27,7 +27,7 @@ func TestQueryDataHandler(t *testing.T) { Version: "v1", Services: []v0alpha1.Service{ { - Type: v0alpha1.DataServiceType, + Type: v0alpha1.QueryServiceType, }, }, }, diff --git a/pkg/aggregator/examples/prometheus.yml b/pkg/aggregator/examples/prometheus.yml index 44379aec765..06e137f7cc7 100644 --- a/pkg/aggregator/examples/prometheus.yml +++ b/pkg/aggregator/examples/prometheus.yml @@ -6,4 +6,4 @@ spec: group: prometheus.grafana.app version: v0alpha1 services: - - type: data + - type: query From 34ab5fe1f3ae7c2da66dbd869f084354261cb3aa Mon Sep 17 00:00:00 2001 From: Alexander Weaver Date: Wed, 14 Aug 2024 14:57:47 -0500 Subject: [PATCH 083/229] Alerting: Restart rule routines if the type changes (#90867) * Restart when types change * Wire up test hooks correctly * testing --- pkg/services/ngalert/models/alert_rule.go | 4 +- pkg/services/ngalert/models/testing.go | 6 +- pkg/services/ngalert/schedule/alert_rule.go | 8 ++ .../ngalert/schedule/recording_rule.go | 29 ++++- .../ngalert/schedule/recording_rule_test.go | 2 +- pkg/services/ngalert/schedule/registry.go | 5 +- pkg/services/ngalert/schedule/schedule.go | 14 +++ .../ngalert/schedule/schedule_unit_test.go | 106 +++++++++++++++++- 8 files changed, 160 insertions(+), 14 deletions(-) diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 127e85c1842..f920a3902b5 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -108,8 +108,8 @@ const ( type RuleType string const ( - RuleTypeAlerting = "alerting" - RuleTypeRecording = "recording" + RuleTypeAlerting RuleType = "alerting" + RuleTypeRecording RuleType = "recording" ) func (r RuleType) String() string { diff --git a/pkg/services/ngalert/models/testing.go b/pkg/services/ngalert/models/testing.go index 657d0546644..f3281f83af8 100644 --- a/pkg/services/ngalert/models/testing.go +++ b/pkg/services/ngalert/models/testing.go @@ -495,13 +495,13 @@ func (a *AlertRuleMutators) WithRandomRecordingRules() AlertRuleMutator { if rand.Int63()%2 == 0 { return } - convertToRecordingRule(rule) + ConvertToRecordingRule(rule) } } func (a *AlertRuleMutators) WithAllRecordingRules() AlertRuleMutator { return func(rule *AlertRule) { - convertToRecordingRule(rule) + ConvertToRecordingRule(rule) } } @@ -1092,7 +1092,7 @@ func (n SilenceMutators) WithEmptyId() Mutator[Silence] { } } -func convertToRecordingRule(rule *AlertRule) { +func ConvertToRecordingRule(rule *AlertRule) { if rule.Record == nil { rule.Record = &Record{} } diff --git a/pkg/services/ngalert/schedule/alert_rule.go b/pkg/services/ngalert/schedule/alert_rule.go index c96a1515db8..b19cd4976b7 100644 --- a/pkg/services/ngalert/schedule/alert_rule.go +++ b/pkg/services/ngalert/schedule/alert_rule.go @@ -38,6 +38,8 @@ type Rule interface { Eval(eval *Evaluation) (bool, *Evaluation) // Update sends a singal to change the definition of the rule. Update(lastVersion RuleVersionAndPauseStatus) bool + // Type gives the type of the rule. + Type() ngmodels.RuleType } type ruleFactoryFunc func(context.Context, *ngmodels.AlertRule) Rule @@ -76,6 +78,8 @@ func newRuleFactory( met, tracer, recordingWriter, + evalAppliedHook, + stopAppliedHook, ) } return newAlertRule( @@ -172,6 +176,10 @@ func newAlertRule( } } +func (a *alertRule) Type() ngmodels.RuleType { + return ngmodels.RuleTypeAlerting +} + // eval signals the rule evaluation routine to perform the evaluation of the rule. Does nothing if the loop is stopped. // Before sending a message into the channel, it does non-blocking read to make sure that there is no concurrent send operation. // Returns a tuple where first element is diff --git a/pkg/services/ngalert/schedule/recording_rule.go b/pkg/services/ngalert/schedule/recording_rule.go index cb9878f40ea..74c248f2ff1 100644 --- a/pkg/services/ngalert/schedule/recording_rule.go +++ b/pkg/services/ngalert/schedule/recording_rule.go @@ -13,7 +13,6 @@ import ( "go.opentelemetry.io/otel/trace" "go.uber.org/atomic" - "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -50,13 +49,14 @@ type recordingRule struct { // Event hooks that are only used in tests. evalAppliedHook evalAppliedFunc + stopAppliedHook stopAppliedFunc logger log.Logger metrics *metrics.Scheduler tracer tracing.Tracer } -func newRecordingRule(parent context.Context, key ngmodels.AlertRuleKey, maxAttempts int64, clock clock.Clock, evalFactory eval.EvaluatorFactory, ft featuremgmt.FeatureToggles, logger log.Logger, metrics *metrics.Scheduler, tracer tracing.Tracer, writer RecordingWriter) *recordingRule { +func newRecordingRule(parent context.Context, key ngmodels.AlertRuleKey, maxAttempts int64, clock clock.Clock, evalFactory eval.EvaluatorFactory, ft featuremgmt.FeatureToggles, logger log.Logger, metrics *metrics.Scheduler, tracer tracing.Tracer, writer RecordingWriter, evalAppliedHook evalAppliedFunc, stopAppliedHook stopAppliedFunc) *recordingRule { ctx, stop := util.WithCancelCause(ngmodels.WithRuleKey(parent, key)) return &recordingRule{ key: key, @@ -71,6 +71,8 @@ func newRecordingRule(parent context.Context, key ngmodels.AlertRuleKey, maxAtte evalFactory: evalFactory, featureToggles: ft, maxAttempts: maxAttempts, + evalAppliedHook: evalAppliedHook, + stopAppliedHook: stopAppliedHook, logger: logger.FromContext(ctx), metrics: metrics, tracer: tracer, @@ -78,6 +80,10 @@ func newRecordingRule(parent context.Context, key ngmodels.AlertRuleKey, maxAtte } } +func (r *recordingRule) Type() ngmodels.RuleType { + return ngmodels.RuleTypeRecording +} + func (r *recordingRule) Status() RuleStatus { return RuleStatus{ Health: r.health.Load(), @@ -115,17 +121,19 @@ func (r *recordingRule) Stop(reason error) { func (r *recordingRule) Run() error { ctx := r.ctx - logger.Debug("Recording rule routine started") + r.logger.Debug("Recording rule routine started") + + defer r.stopApplied() for { select { case eval, ok := <-r.evalCh: if !ok { - logger.Debug("Evaluation channel has been closed. Exiting") + r.logger.Debug("Evaluation channel has been closed. Exiting") return nil } if !r.featureToggles.IsEnabled(ctx, featuremgmt.FlagGrafanaManagedRecordingRules) { - logger.Warn("Recording rule scheduled but toggle is not enabled. Skipping") + r.logger.Warn("Recording rule scheduled but toggle is not enabled. Skipping") return nil } // TODO: Skipping the "evalRunning" guard that the alert rule routine does, because it seems to be dead code and impossible to hit. @@ -133,7 +141,7 @@ func (r *recordingRule) Run() error { r.doEvaluate(ctx, eval) case <-ctx.Done(): - logger.Debug("Stopping recording rule routine") + r.logger.Debug("Stopping recording rule routine") return nil } } @@ -313,3 +321,12 @@ func (r *recordingRule) frameRef(refID string, resp *backend.QueryDataResponse) return targetNode.Frames, nil } + +// stopApplied is only used on tests. +func (r *recordingRule) stopApplied() { + if r.stopAppliedHook == nil { + return + } + + r.stopAppliedHook(r.key) +} diff --git a/pkg/services/ngalert/schedule/recording_rule_test.go b/pkg/services/ngalert/schedule/recording_rule_test.go index 8dba179927d..fde2f1d65fc 100644 --- a/pkg/services/ngalert/schedule/recording_rule_test.go +++ b/pkg/services/ngalert/schedule/recording_rule_test.go @@ -154,7 +154,7 @@ func TestRecordingRule(t *testing.T) { func blankRecordingRuleForTests(ctx context.Context) *recordingRule { ft := featuremgmt.WithFeatures(featuremgmt.FlagGrafanaManagedRecordingRules) - return newRecordingRule(context.Background(), models.AlertRuleKey{}, 0, nil, nil, ft, log.NewNopLogger(), nil, nil, writer.FakeWriter{}) + return newRecordingRule(context.Background(), models.AlertRuleKey{}, 0, nil, nil, ft, log.NewNopLogger(), nil, nil, writer.FakeWriter{}, nil, nil) } func TestRecordingRule_Integration(t *testing.T) { diff --git a/pkg/services/ngalert/schedule/registry.go b/pkg/services/ngalert/schedule/registry.go index 576b51045d6..157124373f5 100644 --- a/pkg/services/ngalert/schedule/registry.go +++ b/pkg/services/ngalert/schedule/registry.go @@ -15,7 +15,10 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/models" ) -var errRuleDeleted = errors.New("rule deleted") +var ( + errRuleDeleted = errors.New("rule deleted") + errRuleRestarted = errors.New("rule restarted") +) type ruleFactory interface { new(context.Context, *models.AlertRule) Rule diff --git a/pkg/services/ngalert/schedule/schedule.go b/pkg/services/ngalert/schedule/schedule.go index 1772cbf8bb5..414d44c1f74 100644 --- a/pkg/services/ngalert/schedule/schedule.go +++ b/pkg/services/ngalert/schedule/schedule.go @@ -255,6 +255,7 @@ func (sch *schedule) processTick(ctx context.Context, dispatcherGroup *errgroup. readyToRun := make([]readyToRunItem, 0) updatedRules := make([]ngmodels.AlertRuleKeyWithVersion, 0, len(updated)) // this is needed for tests only + restartedRules := make([]Rule, 0) missingFolder := make(map[string][]string) ruleFactory := newRuleFactory( sch.appURL, @@ -286,6 +287,14 @@ func (sch *schedule) processTick(ctx context.Context, dispatcherGroup *errgroup. invalidInterval := item.IntervalSeconds%int64(sch.baseInterval.Seconds()) != 0 + if item.Type() != ruleRoutine.Type() { + // Restart rules that need it. For now we just replace them, we'll shut them down at the end of the tick. + logger.Debug("Rule restarted because type changed", "old", ruleRoutine.Type(), "new", item.Type()) + restartedRules = append(restartedRules, ruleRoutine) + sch.registry.del(key) + ruleRoutine, newRoutine = sch.registry.getOrCreate(ctx, item, ruleFactory) + } + if newRoutine && !invalidInterval { dispatcherGroup.Go(func() error { return ruleRoutine.Run() @@ -370,6 +379,11 @@ func (sch *schedule) processTick(ctx context.Context, dispatcherGroup *errgroup. }) } + // Stop old routines for rules that got restarted. + for _, oldRoutine := range restartedRules { + oldRoutine.Stop(errRuleRestarted) + } + // unregister and stop routines of the deleted alert rules toDelete := make([]ngmodels.AlertRuleKey, 0, len(registeredDefinitions)) for key := range registeredDefinitions { diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index 7e94bee11a1..d8a6ecd432e 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -74,6 +74,7 @@ func TestProcessTicks(t *testing.T) { RuleStore: ruleStore, Metrics: testMetrics.GetSchedulerMetrics(), AlertSender: notifier, + FeatureToggles: featuremgmt.WithFeatures(featuremgmt.FlagGrafanaManagedRecordingRules), Tracer: testTracer, Log: log.New("ngalert.scheduler"), } @@ -367,10 +368,101 @@ func TestProcessTicks(t *testing.T) { require.Len(t, updated, 1) require.Equal(t, expectedUpdated, updated[0]) }) - t.Run("on 12th tick all rules should be stopped", func(t *testing.T) { + + // Add a recording rule with 2 * base interval. + recordingRule1 := gen.With(gen.WithOrgID(mainOrgID), gen.WithInterval(2*cfg.BaseInterval), gen.WithTitle("recording-1"), gen.WithAllRecordingRules()).GenerateRef() + ruleStore.PutRule(ctx, recordingRule1) + + t.Run("on 12th tick recording rule and alert rules should be evaluated", func(t *testing.T) { + tick = tick.Add(cfg.BaseInterval) + + scheduled, stopped, updated := sched.processTick(ctx, dispatcherGroup, tick) + + require.Len(t, scheduled, 3) + require.Emptyf(t, stopped, "No rules are expected to be stopped") + require.Emptyf(t, updated, "No rules are expected to be updated") + contains := false + for _, sch := range scheduled { + if sch.rule.Title == recordingRule1.Title { + contains = true + } + } + require.True(t, contains, "Expected a scheduled rule with title %s but didn't get one, scheduled rules were %v", recordingRule1.Title, scheduled) + }) + + // Update the recording rule. + recordingRule1 = models.CopyRule(recordingRule1) + recordingRule1.Version++ + expectedUpdated := models.AlertRuleKeyWithVersion{ + Version: recordingRule1.Version, + AlertRuleKey: recordingRule1.GetKey(), + } + ruleStore.PutRule(context.Background(), recordingRule1) + + t.Run("on 13th tick recording rule should be updated", func(t *testing.T) { + // It has 2 * base interval - so normally it would not have been scheduled for evaluation this tick. + tick = tick.Add(cfg.BaseInterval) + scheduled, stopped, updated := sched.processTick(ctx, dispatcherGroup, tick) + + require.Len(t, scheduled, 1) + require.Emptyf(t, stopped, "No rules are expected to be stopped") + require.Len(t, updated, 1) + require.Equal(t, expectedUpdated, updated[0]) + assertScheduledContains(t, scheduled, alertRule3) + }) + + t.Run("on 14th tick both 1-tick alert rule and 2-tick recording rule should be evaluated", func(t *testing.T) { + tick = tick.Add(cfg.BaseInterval) + + scheduled, stopped, updated := sched.processTick(ctx, dispatcherGroup, tick) + + require.Len(t, scheduled, 2) + require.Emptyf(t, stopped, "No rules are expected to be stopped") + require.Emptyf(t, updated, "No rules are expected to be updated") + assertScheduledContains(t, scheduled, alertRule3) + assertScheduledContains(t, scheduled, recordingRule1) + }) + + // Convert an alerting rule to a recording rule. + models.ConvertToRecordingRule(alertRule3) + alertRule3.Version++ + ruleStore.PutRule(ctx, alertRule3) + + t.Run("prior to 15th tick alertRule3 should still be scheduled as alerting rule", func(t *testing.T) { + require.Equal(t, models.RuleTypeAlerting, sched.registry.rules[alertRule3.GetKey()].Type()) + }) + + t.Run("on 15th tick converted rule and 3-tick alert rule should be evaluated", func(t *testing.T) { + tick = tick.Add(cfg.BaseInterval) + scheduled, stopped, updated := sched.processTick(ctx, dispatcherGroup, tick) + + require.Len(t, scheduled, 2) + require.Emptyf(t, stopped, "No rules are expected to be stopped") + // We never sent the Updated command to the restarted rule, so this should be empty. + require.Emptyf(t, updated, "No rules are expected to be updated") + + assertScheduledContains(t, scheduled, alertRule2) + assertScheduledContains(t, scheduled, alertRule3) // converted + // Rule in registry should be updated to the correct type. + require.Equal(t, models.RuleTypeRecording, sched.registry.rules[alertRule3.GetKey()].Type()) + }) + + t.Run("on 16th tick converted rule and 2-tick recording rule should be evaluated", func(t *testing.T) { + tick = tick.Add(cfg.BaseInterval) + scheduled, stopped, updated := sched.processTick(ctx, dispatcherGroup, tick) + + require.Len(t, scheduled, 2) + require.Emptyf(t, stopped, "No rules are expected to be stopped") + require.Emptyf(t, updated, "No rules are expected to be updated") + assertScheduledContains(t, scheduled, recordingRule1) + assertScheduledContains(t, scheduled, alertRule3) + }) + + t.Run("on 17th tick all rules should be stopped", func(t *testing.T) { expectedToBeStopped, err := ruleStore.GetAlertRulesKeysForScheduling(ctx) require.NoError(t, err) + // Remove all rules from store. ruleStore.rules = map[string]*models.AlertRule{} tick = tick.Add(cfg.BaseInterval) scheduled, stopped, updated := sched.processTick(ctx, dispatcherGroup, tick) @@ -899,3 +991,15 @@ func assertStopRun(t *testing.T, ch <-chan models.AlertRuleKey, keys ...models.A } } } + +func assertScheduledContains(t *testing.T, scheduled []readyToRunItem, rule *models.AlertRule) { + t.Helper() + + contains := false + for _, sch := range scheduled { + if sch.rule.GetKey() == rule.GetKey() { + contains = true + } + } + require.True(t, contains, "Expected a scheduled rule with key %s title %s but didn't get one, scheduled rules were %v", rule.GetKey(), rule.Title, scheduled) +} From 07500e11bed75a334b0fb201490d718faa467862 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Wed, 14 Aug 2024 17:03:19 -0300 Subject: [PATCH 084/229] ShareExternally: Rename public dashboard section (#91928) --- .../app/core/utils/navBarItem-translations.ts | 9 ++- .../ConfigPublicDashboard.tsx | 1 - .../DeletePublicDashboardButton.tsx | 9 ++- .../DeletePublicDashboardModal.tsx | 21 +++--- .../PublicDashboardListTable.test.tsx | 39 ----------- .../PublicDashboardListTable.tsx | 66 +++++++++---------- public/locales/en-US/grafana.json | 26 ++++++-- public/locales/pseudo-LOCALE/grafana.json | 26 ++++++-- 8 files changed, 93 insertions(+), 104 deletions(-) diff --git a/public/app/core/utils/navBarItem-translations.ts b/public/app/core/utils/navBarItem-translations.ts index 5589056e0cf..0af9a4519c1 100644 --- a/public/app/core/utils/navBarItem-translations.ts +++ b/public/app/core/utils/navBarItem-translations.ts @@ -1,3 +1,4 @@ +import { config } from '@grafana/runtime'; import { t } from 'app/core/internationalization'; // Maps the ID of the nav item to a translated phrase to later pass to @@ -43,7 +44,9 @@ export function getNavTitle(navId: string | undefined) { case 'reports': return t('nav.reporting.title', 'Reporting'); case 'dashboards/public': - return t('nav.public.title', 'Public dashboards'); + return config.featureToggles.newDashboardSharingComponent + ? t('nav.shared-dashboard.title', 'Shared dashboards') + : t('nav.public.title', 'Public dashboards'); case 'dashboards/recently-deleted': return t('nav.recently-deleted.title', 'Recently deleted'); case 'dashboards/new': @@ -210,6 +213,10 @@ export function getNavSubTitle(navId: string | undefined) { 'nav.snapshots.subtitle', 'Interactive, publically available, point-in-time representations of dashboards' ); + case 'dashboards/public': + return config.featureToggles.newDashboardSharingComponent + ? t('nav.shared-dashboard.subtitle', "Manage your organization's externally shared dashboards") + : undefined; case 'dashboards/library-panels': return t('nav.library-panels.subtitle', 'Reusable panels that can be added to multiple dashboards'); case 'dashboards/recently-deleted': diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx index e88d1bebf84..662a0b29999 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/ConfigPublicDashboard/ConfigPublicDashboard.tsx @@ -264,7 +264,6 @@ export function ConfigPublicDashboard({ publicDashboard, unsupportedDatasources onRevoke={() => { DashboardInteractions.revokePublicDashboardClicked(); showModal(DeletePublicDashboardModal, { - dashboardTitle: dashboard.title, onConfirm: () => onDeletePublicDashboardClick(hideModal), onDismiss: () => { showModal(ShareModal, { diff --git a/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardButton.tsx b/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardButton.tsx index b91f8e212aa..4156c57ebab 100644 --- a/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardButton.tsx +++ b/public/app/features/manage-dashboards/components/PublicDashboardListTable/DeletePublicDashboardButton.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; +import { config } from '@grafana/runtime'; import { Button, ModalsController, ButtonProps } from '@grafana/ui/src'; import { t } from 'app/core/internationalization'; import { useDeletePublicDashboardMutation } from 'app/features/dashboard/api/publicDashboardApi'; @@ -41,17 +42,15 @@ export const DeletePublicDashboardButton = ({ return ( {({ showModal, hideModal }) => { - const translatedRevocationButtonText = t( - 'public-dashboard-list.button.revoke-button-text', - 'Revoke public URL' - ); + const translatedRevocationButtonText = config.featureToggles.newDashboardSharingComponent + ? t('shared-dashboard-list.button.revoke-button-text', 'Revoke access') + : t('public-dashboard-list.button.revoke-button-text', 'Revoke public URL'); return ( + + + ); +}; diff --git a/public/app/features/auth-config/components/ServerDiscoveryModal.tsx b/public/app/features/auth-config/components/ServerDiscoveryModal.tsx new file mode 100644 index 00000000000..e54cdf0c213 --- /dev/null +++ b/public/app/features/auth-config/components/ServerDiscoveryModal.tsx @@ -0,0 +1,71 @@ +import { useForm } from 'react-hook-form'; + +import { Button, Input, Field, Modal } from '@grafana/ui'; + +import { Trans } from '../../../core/internationalization'; +import { ServerDiscoveryFormData } from '../types'; +import { isUrlValid } from '../utils/url'; + +interface Props { + isOpen: boolean | undefined; + onClose: () => void; + onSuccess: (data: ServerDiscoveryFormData) => void; + isLoading: boolean; +} + +export const ServerDiscoveryModal = ({ isOpen, onClose, onSuccess, isLoading }: Props) => { + const { + handleSubmit, + register, + formState: { errors }, + } = useForm({ + mode: 'onBlur', + defaultValues: { + url: '', + }, + }); + + const validateUrl = (value?: string) => { + if (value === '') { + return 'Please enter the .well-known/openid-configuration endpoint for your IdP'; + } + + if (!isUrlValid(value)) { + return 'Please enter a valid URL'; + } + + return true; + }; + + return ( + +
{ + e.stopPropagation(); + return handleSubmit(onSuccess)(e); + }} + > + + + + + + + +
+
+ ); +}; diff --git a/public/app/features/auth-config/fields.tsx b/public/app/features/auth-config/fields.tsx index e210d39707d..5f81ee11bde 100644 --- a/public/app/features/auth-config/fields.tsx +++ b/public/app/features/auth-config/fields.tsx @@ -4,6 +4,7 @@ import { config } from '@grafana/runtime'; import { TextLink } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; +import { ServerDiscoveryField } from './components/ServerDiscoveryField'; import { FieldData, SSOProvider, SSOSettingsField } from './types'; import { isSelectableValue } from './utils/guards'; import { isUrlValid } from './utils/url'; @@ -67,6 +68,7 @@ export const sectionFields: Section = { 'clientSecret', 'authStyle', 'scopes', + 'serverDiscoveryUrl', 'authUrl', 'tokenUrl', 'apiUrl', @@ -620,6 +622,13 @@ export function fieldMap(provider: string): Record { 'If enabled, Grafana will match the Hosted Domain retrieved from the Google ID Token against the Allowed Domains list specified by the user.', type: 'checkbox', }, + serverDiscoveryUrl: { + label: 'OpenID Connect Discovery URL', + description: + 'The .well-known/openid-configuration endpoint for your IdP. The info extracted from this URL will be used to populate the Auth URL, Token URL and API URL fields.', + type: 'custom', + content: (setValue) => , + }, }; } diff --git a/public/app/features/auth-config/types.ts b/public/app/features/auth-config/types.ts index 19c827fd356..e35622b06c5 100644 --- a/public/app/features/auth-config/types.ts +++ b/public/app/features/auth-config/types.ts @@ -1,5 +1,6 @@ import { ReactElement } from 'react'; import { Validate } from 'react-hook-form'; +import { UseFormSetValue } from 'react-hook-form/dist/types/form'; import { IconName, SelectableValue } from '@grafana/data'; import { Settings } from 'app/types'; @@ -72,6 +73,7 @@ export type SSOProvider = { allowedGroups?: string; scopes?: string; orgMapping?: string; + serverDiscoveryUrl?: string; }; }; @@ -83,6 +85,7 @@ export type SSOProviderDTO = Partial & { allowedGroups?: Array>; scopes?: Array>; orgMapping?: Array>; + serverDiscoveryUrl?: string; }; export interface AuthConfigState { @@ -123,8 +126,13 @@ export type FieldData = { placeholder?: string; defaultValue?: SelectableValue; hidden?: boolean; + content?: (setValue: UseFormSetValue) => ReactElement; }; export type SSOSettingsField = | keyof SSOProvider['settings'] | { name: keyof SSOProvider['settings']; dependsOn: keyof SSOProvider['settings']; hidden?: boolean }; + +export interface ServerDiscoveryFormData { + url: string; +} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index d71b34de7ae..e813d8380fe 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1604,6 +1604,14 @@ "starred-dashboard": "Dashboard starred", "unstarred-dashboard": "Dashboard unstarred" }, + "oauth": { + "form": { + "server-discovery-action-button": "Enter OpenID Connect Discovery URL", + "server-discovery-modal-close": "Close", + "server-discovery-modal-loading": "Loading...", + "server-discovery-modal-submit": "Submit" + } + }, "panel": { "header-menu": { "copy": "Copy", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 20588e9ddd4..f8591176bf9 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -1604,6 +1604,14 @@ "starred-dashboard": "Đäşĥþőäřđ şŧäřřęđ", "unstarred-dashboard": "Đäşĥþőäřđ ūʼnşŧäřřęđ" }, + "oauth": { + "form": { + "server-discovery-action-button": "Ēʼnŧęř ØpęʼnĨĐ Cőʼnʼnęčŧ Đįşčővęřy ŮŖĿ", + "server-discovery-modal-close": "Cľőşę", + "server-discovery-modal-loading": "Ŀőäđįʼnģ...", + "server-discovery-modal-submit": "Ŝūþmįŧ" + } + }, "panel": { "header-menu": { "copy": "Cőpy", From 42efb14989b0b5542bc113da36f518a1e9d216a4 Mon Sep 17 00:00:00 2001 From: Joao Silva <100691367+JoaoSilvaGrafana@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:35:20 +0100 Subject: [PATCH 094/229] Bookmarks: Move building logic to the Frontend (#91849) --- pkg/services/navtree/navtreeimpl/navtree.go | 37 +----- .../AppChrome/MegaMenu/MegaMenu.tsx | 22 +++- .../AppChrome/MegaMenu/utils.test.ts | 110 +++++++++++------- 3 files changed, 88 insertions(+), 81 deletions(-) diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index c63f6211180..c6268905661 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -169,14 +169,12 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere } if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagPinNavItems) { - bookmarks := s.buildBookmarksNavLinks(prefs, treeRoot) - treeRoot.AddSection(&navtree.NavLink{ Text: "Bookmarks", Id: navtree.NavIDBookmarks, Icon: "bookmark", SortWeight: navtree.WeightBookmarks, - Children: bookmarks, + Children: []*navtree.NavLink{}, EmptyMessageId: "bookmarks-empty", Url: s.cfg.AppSubURL + "/bookmarks", }) @@ -335,39 +333,6 @@ func (s *ServiceImpl) buildStarredItemsNavLinks(c *contextmodel.ReqContext) ([]* return starredItemsChildNavs, nil } -func (s *ServiceImpl) buildBookmarksNavLinks(prefs *pref.Preference, treeRoot *navtree.NavTreeRoot) []*navtree.NavLink { - bookmarksChildNavs := []*navtree.NavLink{} - - bookmarkUrls := prefs.JSONData.Navbar.BookmarkUrls - - if len(bookmarkUrls) > 0 { - for _, url := range bookmarkUrls { - item := treeRoot.FindByURL(url) - if item != nil { - bookmarksChildNavs = append(bookmarksChildNavs, &navtree.NavLink{ - Id: item.Id, - Text: item.Text, - SubTitle: item.SubTitle, - Icon: item.Icon, - Img: item.Img, - Url: item.Url, - Target: item.Target, - HideFromTabs: item.HideFromTabs, - RoundIcon: item.RoundIcon, - IsSection: item.IsSection, - HighlightText: item.HighlightText, - HighlightID: item.HighlightID, - PluginID: item.PluginID, - IsCreateAction: item.IsCreateAction, - Keywords: item.Keywords, - ParentItem: &navtree.NavLink{Id: navtree.NavIDBookmarks}, - }) - } - } - } - - return bookmarksChildNavs -} func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink { hasAccess := ac.HasAccess(s.accessControl, c) diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx index ce7488d0d9b..d3bed574dcc 100644 --- a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx @@ -15,7 +15,7 @@ import { useDispatch, useSelector } from 'app/types'; import { MegaMenuItem } from './MegaMenuItem'; import { usePinnedItems } from './hooks'; -import { enrichWithInteractionTracking, getActiveItem } from './utils'; +import { enrichWithInteractionTracking, findByUrl, getActiveItem } from './utils'; export const MENU_WIDTH = '300px'; @@ -39,6 +39,26 @@ export const MegaMenu = memo( .filter((item) => item.id !== 'profile' && item.id !== 'help') .map((item) => enrichWithInteractionTracking(item, state.megaMenuDocked)); + if (config.featureToggles.pinNavItems) { + const bookmarksItem = findByUrl(navItems, '/bookmarks'); + if (bookmarksItem) { + // Add children to the bookmarks section + bookmarksItem.children = pinnedItems.reduce((acc: NavModelItem[], url) => { + const item = findByUrl(navItems, url); + if (!item) { + return acc; + } + acc.push({ + id: item.id, + text: item.text, + url: item.url, + parentItem: { id: 'bookmarks', text: 'Bookmarks' }, + }); + return acc; + }, []); + } + } + const activeItem = getActiveItem(navItems, state.sectionNav.node, location.pathname); const handleDockedMenu = () => { diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts index e03c46022e7..0a99bffd6b3 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts @@ -1,7 +1,50 @@ import { NavModelItem } from '@grafana/data'; import { ContextSrv, setContextSrv } from 'app/core/services/context_srv'; -import { enrichHelpItem, getActiveItem } from './utils'; +import { enrichHelpItem, getActiveItem, findByUrl } from './utils'; + +const starredDashboardUid = 'foo'; +const mockNavTree: NavModelItem[] = [ + { + text: 'Item', + url: '/item', + id: 'item', + }, + { + text: 'Item with children', + url: '/itemWithChildren', + id: 'item-with-children', + children: [ + { + text: 'Child', + url: '/child', + id: 'child', + }, + ], + }, + { + text: 'Base', + url: '/', + id: 'home', + }, + { + text: 'Starred', + url: '/dashboards?starred', + id: 'starred', + children: [ + { + id: `starred/${starredDashboardUid}`, + text: 'Lazy Loading', + url: `/d/${starredDashboardUid}/some-name`, + }, + ], + }, + { + text: 'Dashboards', + url: '/dashboards', + id: 'dashboards', + }, +]; jest.mock('../../../app_events', () => ({ publish: jest.fn(), @@ -45,49 +88,6 @@ describe('enrichConfigItems', () => { }); describe('getActiveItem', () => { - const starredDashboardUid = 'foo'; - const mockNavTree: NavModelItem[] = [ - { - text: 'Item', - url: '/item', - id: 'item', - }, - { - text: 'Item with children', - url: '/itemWithChildren', - id: 'item-with-children', - children: [ - { - text: 'Child', - url: '/child', - id: 'child', - }, - ], - }, - { - text: 'Base', - url: '/', - id: 'home', - }, - { - text: 'Starred', - url: '/dashboards?starred', - id: 'starred', - children: [ - { - id: `starred/${starredDashboardUid}`, - text: 'Lazy Loading', - url: `/d/${starredDashboardUid}/some-name`, - }, - ], - }, - { - text: 'Dashboards', - url: '/dashboards', - id: 'dashboards', - }, - ]; - it('returns an exact match at the top level', () => { const mockPage: NavModelItem = { text: 'Some current page', @@ -124,3 +124,25 @@ describe('getActiveItem', () => { expect(getActiveItem(mockNavTree, mockPage, '/')?.id).toEqual('home'); }); }); + +describe('findByUrl', () => { + it('returns the correct item at the top level', () => { + expect(findByUrl(mockNavTree, '/item')).toEqual({ + text: 'Item', + url: '/item', + id: 'item', + }); + }); + + it('returns the correct child item', () => { + expect(findByUrl(mockNavTree, '/child')).toEqual({ + text: 'Child', + url: '/child', + id: 'child', + }); + }); + + it('returns null if no item found', () => { + expect(findByUrl(mockNavTree, '/no-item')).toBeNull(); + }); +}); From 868f9320e9c2a088c6e3106750dc34ccd3e0a1b2 Mon Sep 17 00:00:00 2001 From: Mehrshad Lotfi Date: Thu, 15 Aug 2024 15:02:15 +0200 Subject: [PATCH 095/229] CI: Fix missing vendor dependencies (#91872) * Add missing dependency to Dockerfile Add aggregator dependency to Dockerfile, fix the issue #91871. * Add defaults.ini to Dockerfile, add bash for alpine --- Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Dockerfile b/Dockerfile index 416b2a5b5b8..5fe3fc478aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,7 @@ COPY packages packages COPY plugins-bundled plugins-bundled COPY public public COPY LICENSE ./ +COPY conf/defaults.ini ./conf/defaults.ini RUN apk add --no-cache make build-base python3 @@ -44,6 +45,7 @@ RUN if grep -i -q alpine /etc/issue; then \ apk add --no-cache \ # This is required to allow building on arm64 due to https://github.com/golang/go/issues/22040 binutils-gold \ + bash \ # Install build dependencies gcc g++ make git; \ fi @@ -62,6 +64,7 @@ COPY pkg/build/wire/go.* pkg/build/wire/ COPY pkg/promlib/go.* pkg/promlib/ COPY pkg/storage/unified/resource/go.* pkg/storage/unified/resource/ COPY pkg/semconv/go.* pkg/semconv/ +COPY pkg/aggregator/go.* pkg/aggregator/ RUN go mod download RUN if [[ "$BINGO" = "true" ]]; then \ From 5834981f86ed0977a586177f93138890f8ef0cb7 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Thu, 15 Aug 2024 09:17:48 -0400 Subject: [PATCH 096/229] Alerting: Add GetTemplate to template service and update tests (#91854) * add GetTemplate to template service * refactor GetTemplates to fetch all provenances at once * refactor tests --- pkg/services/ngalert/api/api_provisioning.go | 1 + .../notification_policies_test.go | 2 +- .../ngalert/provisioning/templates.go | 43 +- .../ngalert/provisioning/templates_test.go | 1071 +++++++++-------- 4 files changed, 636 insertions(+), 481 deletions(-) diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index ed645537d34..262c8a05753 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -45,6 +45,7 @@ type ContactPointService interface { type TemplateService interface { GetTemplates(ctx context.Context, orgID int64) ([]definitions.NotificationTemplate, error) + GetTemplate(ctx context.Context, orgID int64, name string) (definitions.NotificationTemplate, error) SetTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) DeleteTemplate(ctx context.Context, orgID int64, name string, provenance definitions.Provenance, version string) error } diff --git a/pkg/services/ngalert/provisioning/notification_policies_test.go b/pkg/services/ngalert/provisioning/notification_policies_test.go index bb2cbf6b2dc..bf9be3b2462 100644 --- a/pkg/services/ngalert/provisioning/notification_policies_test.go +++ b/pkg/services/ngalert/provisioning/notification_policies_test.go @@ -271,7 +271,7 @@ func createTestRoutingTree() definitions.Route { } func createTestAlertingConfig() *definitions.PostableUserConfig { - cfg, _ := legacy_storage.DeserializeAlertmanagerConfig([]byte(defaultConfig)) + cfg, _ := legacy_storage.DeserializeAlertmanagerConfig([]byte(setting.GetAlertmanagerDefaultConfiguration())) cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers, &definitions.PostableApiReceiver{ Receiver: config.Receiver{ diff --git a/pkg/services/ngalert/provisioning/templates.go b/pkg/services/ngalert/provisioning/templates.go index 06c878e4742..bed740aa763 100644 --- a/pkg/services/ngalert/provisioning/templates.go +++ b/pkg/services/ngalert/provisioning/templates.go @@ -36,6 +36,15 @@ func (t *TemplateService) GetTemplates(ctx context.Context, orgID int64) ([]defi return nil, err } + if len(revision.Config.TemplateFiles) == 0 { + return nil, nil + } + + provenances, err := t.provenanceStore.GetProvenances(ctx, orgID, (&definitions.NotificationTemplate{}).ResourceType()) + if err != nil { + return nil, err + } + templates := make([]definitions.NotificationTemplate, 0, len(revision.Config.TemplateFiles)) for name, tmpl := range revision.Config.TemplateFiles { tmpl := definitions.NotificationTemplate{ @@ -43,19 +52,43 @@ func (t *TemplateService) GetTemplates(ctx context.Context, orgID int64) ([]defi Template: tmpl, ResourceVersion: calculateTemplateFingerprint(tmpl), } - - provenance, err := t.provenanceStore.GetProvenance(ctx, &tmpl, orgID) - if err != nil { - return nil, err + provenance, ok := provenances[tmpl.ResourceID()] + if !ok { + provenance = models.ProvenanceNone } tmpl.Provenance = definitions.Provenance(provenance) - templates = append(templates, tmpl) } return templates, nil } +func (t *TemplateService) GetTemplate(ctx context.Context, orgID int64, name string) (definitions.NotificationTemplate, error) { + revision, err := t.configStore.Get(ctx, orgID) + if err != nil { + return definitions.NotificationTemplate{}, err + } + + for tmplName, tmpl := range revision.Config.TemplateFiles { + if tmplName != name { + continue + } + tmpl := definitions.NotificationTemplate{ + Name: name, + Template: tmpl, + ResourceVersion: calculateTemplateFingerprint(tmpl), + } + + provenance, err := t.provenanceStore.GetProvenance(ctx, &tmpl, orgID) + if err != nil { + return definitions.NotificationTemplate{}, err + } + tmpl.Provenance = definitions.Provenance(provenance) + return tmpl, nil + } + return definitions.NotificationTemplate{}, ErrTemplateNotFound.Errorf("") +} + func (t *TemplateService) SetTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) { err := tmpl.Validate() if err != nil { diff --git a/pkg/services/ngalert/provisioning/templates_test.go b/pkg/services/ngalert/provisioning/templates_test.go index 82123fea5b7..2921a8b1499 100644 --- a/pkg/services/ngalert/provisioning/templates_test.go +++ b/pkg/services/ngalert/provisioning/templates_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/stretchr/testify/assert" - mock "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/grafana/grafana/pkg/infra/log" @@ -15,592 +15,713 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage" "github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation" - "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/util" ) -func TestTemplateService(t *testing.T) { - t.Run("service returns templates from config file", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) - - result, err := sut.GetTemplates(context.Background(), 1) +func TestGetTemplates(t *testing.T) { + orgID := int64(1) + revision := &legacy_storage.ConfigRevision{ + Config: &definitions.PostableUserConfig{ + TemplateFiles: map[string]string{ + "template1": "test1", + "template2": "test2", + "template3": "test3", + }, + }, + } + t.Run("returns templates from config file", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + assert.Equal(t, orgID, org) + return revision, nil + } + prov.EXPECT().GetProvenances(mock.Anything, mock.Anything, mock.Anything).Return(map[string]models.Provenance{ + "template1": models.ProvenanceAPI, + "template2": models.ProvenanceFile, + }, nil) + + result, err := sut.GetTemplates(context.Background(), orgID) require.NoError(t, err) - require.Len(t, result, 1) + + expected := []definitions.NotificationTemplate{ + { + Name: "template1", + Template: "test1", + Provenance: definitions.Provenance(models.ProvenanceAPI), + ResourceVersion: calculateTemplateFingerprint("test1"), + }, + { + Name: "template2", + Template: "test2", + Provenance: definitions.Provenance(models.ProvenanceFile), + ResourceVersion: calculateTemplateFingerprint("test2"), + }, + { + Name: "template3", + Template: "test3", + Provenance: definitions.Provenance(models.ProvenanceNone), + ResourceVersion: calculateTemplateFingerprint("test3"), + }, + } + + require.ElementsMatch(t, expected, result) + + prov.AssertCalled(t, "GetProvenances", mock.Anything, orgID, (&definitions.NotificationTemplate{}).ResourceType()) + prov.AssertExpectations(t) }) - t.Run("service returns empty map when config file contains no templates", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: defaultConfig, - }) + t.Run("returns empty list when config file contains no templates", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return &legacy_storage.ConfigRevision{ + Config: &definitions.PostableUserConfig{}, + }, nil + } result, err := sut.GetTemplates(context.Background(), 1) require.NoError(t, err) require.Empty(t, result) + prov.AssertExpectations(t) }) - t.Run("service propagates errors", func(t *testing.T) { + t.Run("propagates errors", func(t *testing.T) { t.Run("when unable to read config", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything). - Return(nil, fmt.Errorf("failed")) + sut, store, prov := createTemplateServiceSut() + expectedErr := errors.New("test") + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return nil, expectedErr + } _, err := sut.GetTemplates(context.Background(), 1) - require.Error(t, err) - }) + require.ErrorIs(t, err, expectedErr) - t.Run("when config is invalid", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: brokenConfig, - }) + prov.AssertExpectations(t) + }) - _, err := sut.GetTemplates(context.Background(), 1) + t.Run("when provenance status fails", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() - require.Truef(t, legacy_storage.ErrBadAlertmanagerConfiguration.Base.Is(err), "expected ErrBadAlertmanagerConfiguration but got %s", err.Error()) - }) + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision, nil + } - t.Run("when no AM config in current org", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything). - Return(nil, nil) + expectedErr := errors.New("test") + prov.EXPECT().GetProvenances(mock.Anything, mock.Anything, mock.Anything).Return(nil, expectedErr) _, err := sut.GetTemplates(context.Background(), 1) - require.Truef(t, legacy_storage.ErrNoAlertmanagerConfiguration.Is(err), "expected ErrNoAlertmanagerConfiguration but got %s", err.Error()) + require.ErrorIs(t, err, expectedErr) + + prov.AssertExpectations(t) }) }) +} - t.Run("setting templates", func(t *testing.T) { - t.Run("rejects templates that fail validation", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - tmpl := definitions.NotificationTemplate{ - Name: "", - Template: "", - } +func TestGetTemplate(t *testing.T) { + orgID := int64(1) + templateName := "template1" + templateContent := "test1" + revision := &legacy_storage.ConfigRevision{ + Config: &definitions.PostableUserConfig{ + TemplateFiles: map[string]string{ + templateName: templateContent, + }, + }, + } - _, err := sut.SetTemplate(context.Background(), 1, tmpl) + t.Run("return a template from config file by name", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + assert.Equal(t, orgID, org) + return revision, nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) - require.ErrorIs(t, err, ErrTemplateInvalid) - }) + result, err := sut.GetTemplate(context.Background(), orgID, templateName) + require.NoError(t, err) + + expected := definitions.NotificationTemplate{ + Name: templateName, + Template: templateContent, + Provenance: definitions.Provenance(models.ProvenanceAPI), + ResourceVersion: calculateTemplateFingerprint(templateContent), + } + + require.Equal(t, expected, result) - t.Run("rejects existing templates if provenance is not right", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) + prov.AssertCalled(t, "GetProvenance", mock.Anything, mock.MatchedBy(func(t *definitions.NotificationTemplate) bool { + return t.Name == expected.Name + }), orgID) + prov.AssertExpectations(t) + }) + + t.Run("returns ErrTemplateNotFound when template does not exist", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + assert.Equal(t, orgID, org) + return revision, nil + } + _, err := sut.GetTemplate(context.Background(), orgID, "not-found") + require.ErrorIs(t, err, ErrTemplateNotFound) + prov.AssertExpectations(t) + }) + t.Run("service propagates errors", func(t *testing.T) { + t.Run("when unable to read config", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() expectedErr := errors.New("test") - sut.validator = func(from, to models.Provenance) error { - assert.Equal(t, models.ProvenanceAPI, from) - assert.Equal(t, models.ProvenanceNone, to) - return expectedErr + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return nil, expectedErr } - template := definitions.NotificationTemplate{ - Name: "a", - Template: "asdf-new", - } - template.Provenance = definitions.Provenance(models.ProvenanceNone) - _, err := sut.SetTemplate(context.Background(), 1, template) + _, err := sut.GetTemplate(context.Background(), 1, templateName) require.ErrorIs(t, err, expectedErr) + + prov.AssertExpectations(t) }) - t.Run("rejects existing templates if version is not right", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - - template := definitions.NotificationTemplate{ - Name: "a", - Template: "asdf-new", - ResourceVersion: "bad-version", - Provenance: definitions.Provenance(models.ProvenanceNone), + t.Run("when provenance status fails", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision, nil } + expectedErr := errors.New("test") + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, expectedErr) - _, err := sut.SetTemplate(context.Background(), 1, template) + _, err := sut.GetTemplate(context.Background(), orgID, templateName) + require.ErrorIs(t, err, expectedErr) - require.ErrorIs(t, err, ErrVersionConflict) + prov.AssertExpectations(t) }) + }) +} - t.Run("rejects new template if version is set", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - tmpl := createNotificationTemplate() - tmpl.ResourceVersion = "test" - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() +func TestSetTemplate(t *testing.T) { + orgID := int64(1) + templateName := "template1" + currentTemplateContent := "test1" + amConfigToken := util.GenerateShortUID() + revision := func() *legacy_storage.ConfigRevision { + return &legacy_storage.ConfigRevision{ + Config: &definitions.PostableUserConfig{ + TemplateFiles: map[string]string{ + templateName: currentTemplateContent, + }, + }, + ConcurrencyToken: amConfigToken, + } + } - _, err := sut.SetTemplate(context.Background(), 1, tmpl) + t.Run("adds new template to config file", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + assert.Equal(t, orgID, org) + return &legacy_storage.ConfigRevision{ + Config: &definitions.PostableUserConfig{}, + ConcurrencyToken: amConfigToken, + }, nil + } + store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error { + assertInTransaction(t, ctx) + return nil + } + prov.EXPECT().SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) { + assertInTransaction(t, ctx) + }).Return(nil) + + tmpl := definitions.NotificationTemplate{ + Name: "new-template", + Template: "{{ define \"test\"}} test {{ end }}", + Provenance: definitions.Provenance(models.ProvenanceAPI), + ResourceVersion: "", + } + + result, err := sut.SetTemplate(context.Background(), orgID, tmpl) - require.ErrorIs(t, err, ErrTemplateNotFound) - }) + require.NoError(t, err) + require.Equal(t, definitions.NotificationTemplate{ + Name: tmpl.Name, + Template: tmpl.Template, + Provenance: tmpl.Provenance, + ResourceVersion: calculateTemplateFingerprint(tmpl.Template), + }, result) + + require.Len(t, store.Calls, 2) + + require.Equal(t, "Save", store.Calls[1].Method) + saved := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision) + assert.Equal(t, amConfigToken, saved.ConcurrencyToken) + assert.Contains(t, saved.Config.TemplateFiles, tmpl.Name) + assert.Equal(t, tmpl.Template, saved.Config.TemplateFiles[tmpl.Name]) + + prov.AssertCalled(t, "SetProvenance", mock.Anything, mock.MatchedBy(func(t *definitions.NotificationTemplate) bool { + return t.Name == tmpl.Name + }), orgID, models.ProvenanceAPI) + }) - t.Run("propagates errors", func(t *testing.T) { - t.Run("when unable to read config", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - tmpl := createNotificationTemplate() - mockStore.EXPECT(). - GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything). - Return(nil, fmt.Errorf("failed")) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - - _, err := sut.SetTemplate(context.Background(), 1, tmpl) - - require.Error(t, err) - }) - - t.Run("when config is invalid", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - tmpl := createNotificationTemplate() - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: brokenConfig, - }) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - - _, err := sut.SetTemplate(context.Background(), 1, tmpl) - - require.Truef(t, legacy_storage.ErrBadAlertmanagerConfiguration.Base.Is(err), "expected ErrBadAlertmanagerConfiguration but got %s", err.Error()) - }) - - t.Run("when no AM config in current org", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - tmpl := createNotificationTemplate() - mockStore.EXPECT(). - GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything). - Return(nil, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - - _, err := sut.SetTemplate(context.Background(), 1, tmpl) - - require.Truef(t, legacy_storage.ErrNoAlertmanagerConfiguration.Is(err), "expected ErrNoAlertmanagerConfiguration but got %s", err.Error()) - }) - - t.Run("when provenance fails to save", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - tmpl := createNotificationTemplate() - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT(). - SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything). - Return(fmt.Errorf("failed to save provenance")) - - _, err := sut.SetTemplate(context.Background(), 1, tmpl) - - require.ErrorContains(t, err, "failed to save provenance") - }) - - t.Run("when AM config fails to save", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - tmpl := createNotificationTemplate() - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - mockStore.EXPECT(). - UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything). - Return(fmt.Errorf("failed to save config")) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - - _, err := sut.SetTemplate(context.Background(), 1, tmpl) - - require.ErrorContains(t, err, "failed to save config") - }) - }) + t.Run("updates current template", func(t *testing.T) { + t.Run("when version matches", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) + prov.EXPECT().SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) { + assertInTransaction(t, ctx) + }).Return(nil) - t.Run("adds new template to config file on success", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - tmpl := createNotificationTemplate() - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() + tmpl := definitions.NotificationTemplate{ + Name: templateName, + Template: "{{ define \"test\"}} test {{ end }}", + Provenance: definitions.Provenance(models.ProvenanceAPI), + ResourceVersion: calculateTemplateFingerprint("test1"), + } - _, err := sut.SetTemplate(context.Background(), 1, tmpl) + result, err := sut.SetTemplate(context.Background(), orgID, tmpl) require.NoError(t, err) + assert.Equal(t, definitions.NotificationTemplate{ + Name: tmpl.Name, + Template: tmpl.Template, + Provenance: tmpl.Provenance, + ResourceVersion: calculateTemplateFingerprint(tmpl.Template), + }, result) + + require.Len(t, store.Calls, 2) + require.Equal(t, "Save", store.Calls[1].Method) + saved := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision) + assert.Equal(t, amConfigToken, saved.ConcurrencyToken) + assert.Contains(t, saved.Config.TemplateFiles, tmpl.Name) + assert.Equal(t, tmpl.Template, saved.Config.TemplateFiles[tmpl.Name]) + + prov.AssertExpectations(t) }) + t.Run("bypasses optimistic concurrency validation when version is empty", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) + prov.EXPECT().SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) { + assertInTransaction(t, ctx) + }).Return(nil) - t.Run("succeeds when stitching config file with no templates", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - tmpl := createNotificationTemplate() - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: defaultConfig, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() + tmpl := definitions.NotificationTemplate{ + Name: templateName, + Template: "{{ define \"test\"}} test {{ end }}", + Provenance: definitions.Provenance(models.ProvenanceAPI), + ResourceVersion: "", + } - _, err := sut.SetTemplate(context.Background(), 1, tmpl) + result, err := sut.SetTemplate(context.Background(), orgID, tmpl) require.NoError(t, err) + assert.Equal(t, definitions.NotificationTemplate{ + Name: tmpl.Name, + Template: tmpl.Template, + Provenance: tmpl.Provenance, + ResourceVersion: calculateTemplateFingerprint(tmpl.Template), + }, result) + + require.Equal(t, "Save", store.Calls[1].Method) + saved := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision) + assert.Equal(t, amConfigToken, saved.ConcurrencyToken) + assert.Contains(t, saved.Config.TemplateFiles, tmpl.Name) + assert.Equal(t, tmpl.Template, saved.Config.TemplateFiles[tmpl.Name]) }) + }) + + t.Run("normalizes template content with no define", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + assert.Equal(t, orgID, org) + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) + prov.EXPECT().SaveSucceeds() + + tmpl := definitions.NotificationTemplate{ + Name: templateName, + Template: "content", + Provenance: definitions.Provenance(models.ProvenanceNone), + ResourceVersion: calculateTemplateFingerprint(currentTemplateContent), + } + + result, _ := sut.SetTemplate(context.Background(), orgID, tmpl) + + expectedContent := fmt.Sprintf("{{ define \"%s\" }}\n content\n{{ end }}", templateName) + require.Equal(t, definitions.NotificationTemplate{ + Name: tmpl.Name, + Template: expectedContent, + Provenance: tmpl.Provenance, + ResourceVersion: calculateTemplateFingerprint(expectedContent), + }, result) + }) + + t.Run("does not reject template with unknown field", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + assert.Equal(t, orgID, org) + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) + prov.EXPECT().SaveSucceeds() + + tmpl := definitions.NotificationTemplate{ + Name: "name", + Template: "{{ .NotAField }}", + } + _, err := sut.SetTemplate(context.Background(), 1, tmpl) + + require.NoError(t, err) + }) + + t.Run("rejects templates that fail validation", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() - t.Run("normalizes template content with no define", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) + t.Run("empty content", func(t *testing.T) { tmpl := definitions.NotificationTemplate{ - Name: "name", - Template: "content", + Name: "", + Template: "", } - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: defaultConfig, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() - - result, _ := sut.SetTemplate(context.Background(), 1, tmpl) - - exp := "{{ define \"name\" }}\n content\n{{ end }}" - require.Equal(t, exp, result.Template) + _, err := sut.SetTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, ErrTemplateInvalid) }) - t.Run("avoids normalizing template content with define", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) + t.Run("invalid content", func(t *testing.T) { tmpl := definitions.NotificationTemplate{ - Name: "name", - Template: "{{define \"name\"}}content{{end}}", + Name: "", + Template: "{{ .MyField }", } - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: defaultConfig, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() + _, err := sut.SetTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, ErrTemplateInvalid) + }) + + require.Empty(t, store.Calls) + prov.AssertExpectations(t) + }) - result, _ := sut.SetTemplate(context.Background(), 1, tmpl) + t.Run("rejects existing templates if provenance is not right", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) + + expectedErr := errors.New("test") + sut.validator = func(from, to models.Provenance) error { + assert.Equal(t, models.ProvenanceAPI, from) + assert.Equal(t, models.ProvenanceNone, to) + return expectedErr + } + + template := definitions.NotificationTemplate{ + Name: "template1", + Template: "asdf-new", + } + template.Provenance = definitions.Provenance(models.ProvenanceNone) + + _, err := sut.SetTemplate(context.Background(), orgID, template) + + require.ErrorIs(t, err, expectedErr) + }) + + t.Run("rejects existing templates if version is not right", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) + + template := definitions.NotificationTemplate{ + Name: "template1", + Template: "asdf-new", + ResourceVersion: "bad-version", + Provenance: definitions.Provenance(models.ProvenanceNone), + } + + _, err := sut.SetTemplate(context.Background(), orgID, template) + + require.ErrorIs(t, err, ErrVersionConflict) + prov.AssertExpectations(t) + }) - require.Equal(t, tmpl.Template, result.Template) + t.Run("rejects new template if version is set", func(t *testing.T) { + sut, store, _ := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + template := definitions.NotificationTemplate{ + Name: "template2", + Template: "asdf-new", + ResourceVersion: "version", + Provenance: definitions.Provenance(models.ProvenanceNone), + } + _, err := sut.SetTemplate(context.Background(), orgID, template) + require.ErrorIs(t, err, ErrTemplateNotFound) + }) + t.Run("propagates errors", func(t *testing.T) { + tmpl := definitions.NotificationTemplate{ + Name: templateName, + Template: "content", + Provenance: definitions.Provenance(models.ProvenanceNone), + } + t.Run("when unable to read config", func(t *testing.T) { + sut, store, _ := createTemplateServiceSut() + expectedErr := errors.New("test") + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return nil, expectedErr + } + + _, err := sut.SetTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, expectedErr) }) - t.Run("rejects syntactically invalid template", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - tmpl := definitions.NotificationTemplate{ - Name: "name", - Template: "{{ .MyField }", + t.Run("when reading provenance status fails", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil } - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: defaultConfig, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() + expectedErr := errors.New("test") + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, expectedErr) - _, err := sut.SetTemplate(context.Background(), 1, tmpl) + _, err := sut.SetTemplate(context.Background(), orgID, tmpl) - require.ErrorIs(t, err, ErrTemplateInvalid) + require.ErrorIs(t, err, expectedErr) + + prov.AssertExpectations(t) }) - t.Run("does not reject template with unknown field", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - tmpl := definitions.NotificationTemplate{ - Name: "name", - Template: "{{ .NotAField }}", + t.Run("when provenance fails to save", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + expectedErr := errors.New("test") + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil } - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: defaultConfig, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) + prov.EXPECT().SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(expectedErr) - _, err := sut.SetTemplate(context.Background(), 1, tmpl) + _, err := sut.SetTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, expectedErr) - require.NoError(t, err) + prov.AssertExpectations(t) }) - }) - t.Run("deleting templates", func(t *testing.T) { - t.Run("propagates errors", func(t *testing.T) { - t.Run("when unable to read config", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything). - Return(nil, fmt.Errorf("failed")) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) - - err := sut.DeleteTemplate(context.Background(), 1, "template", definitions.Provenance(models.ProvenanceAPI), "") - - require.Error(t, err) - }) - - t.Run("when config is invalid", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: brokenConfig, - }) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) - - err := sut.DeleteTemplate(context.Background(), 1, "template", definitions.Provenance(models.ProvenanceAPI), "") - - require.Truef(t, legacy_storage.ErrBadAlertmanagerConfiguration.Base.Is(err), "expected ErrBadAlertmanagerConfiguration but got %s", err.Error()) - }) - - t.Run("when no AM config in current org", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything). - Return(nil, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) - - err := sut.DeleteTemplate(context.Background(), 1, "template", definitions.Provenance(models.ProvenanceAPI), "") - - require.Truef(t, legacy_storage.ErrNoAlertmanagerConfiguration.Is(err), "expected ErrNoAlertmanagerConfiguration but got %s", err.Error()) - }) - - t.Run("when provenance fails to save", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT(). - DeleteProvenance(mock.Anything, mock.Anything, mock.Anything). - Return(fmt.Errorf("failed to save provenance")) - - err := sut.DeleteTemplate(context.Background(), 1, "a", definitions.Provenance(models.ProvenanceAPI), "") - - require.ErrorContains(t, err, "failed to save provenance") - }) - - t.Run("when AM config fails to save", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - mockStore.EXPECT(). - UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything). - Return(fmt.Errorf("failed to save config")) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() - - err := sut.DeleteTemplate(context.Background(), 1, "a", definitions.Provenance(models.ProvenanceAPI), "") - - require.ErrorContains(t, err, "failed to save config") - }) + t.Run("when AM config fails to save", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + expectedErr := errors.New("test") + store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error { + return expectedErr + } + prov.EXPECT().SaveSucceeds() + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) + + _, err := sut.SetTemplate(context.Background(), 1, tmpl) + require.ErrorIs(t, err, expectedErr) }) + }) +} - t.Run("deletes template from config file on success", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() +func TestDeleteTemplate(t *testing.T) { + orgID := int64(1) + templateName := "template1" + templateContent := "test-1" + templateVersion := calculateTemplateFingerprint(templateContent) + amConfigToken := util.GenerateShortUID() + revision := func() *legacy_storage.ConfigRevision { + return &legacy_storage.ConfigRevision{ + Config: &definitions.PostableUserConfig{ + TemplateFiles: map[string]string{ + templateName: templateContent, + }, + }, + ConcurrencyToken: amConfigToken, + } + } + + t.Run("deletes template from config file on success", func(t *testing.T) { + t.Run("when version matches", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceFile, nil) + prov.EXPECT().DeleteProvenance(mock.Anything, mock.Anything, mock.Anything).Run(func(ctx context.Context, o models.Provisionable, org int64) { + assertInTransaction(t, ctx) + }).Return(nil) - err := sut.DeleteTemplate(context.Background(), 1, "a", definitions.Provenance(models.ProvenanceAPI), "") + err := sut.DeleteTemplate(context.Background(), orgID, templateName, definitions.Provenance(models.ProvenanceFile), templateVersion) require.NoError(t, err) - }) - t.Run("deletes template from config file on success ignoring optimistic concurrency", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() + require.Len(t, store.Calls, 2) - err := sut.DeleteTemplate(context.Background(), 1, "a", definitions.Provenance(models.ProvenanceAPI), "b26e328af4bb9aaf") + require.Equal(t, "Save", store.Calls[1].Method) + saved := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision) + assert.Equal(t, amConfigToken, saved.ConcurrencyToken) + assert.NotContains(t, saved.Config.TemplateFiles, templateName) - require.NoError(t, err) + prov.AssertCalled(t, "DeleteProvenance", mock.Anything, mock.MatchedBy(func(t *definitions.NotificationTemplate) bool { + return t.Name == templateName + }), orgID) + + prov.AssertExpectations(t) }) - t.Run("does not error when deleting templates that do not exist", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() + t.Run("bypasses optimistic concurrency when version is empty", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceFile, nil) + prov.EXPECT().DeleteProvenance(mock.Anything, mock.Anything, mock.Anything).Run(func(ctx context.Context, o models.Provisionable, org int64) { + assertInTransaction(t, ctx) + }).Return(nil) - err := sut.DeleteTemplate(context.Background(), 1, "does not exist", definitions.Provenance(models.ProvenanceAPI), "") + err := sut.DeleteTemplate(context.Background(), orgID, templateName, definitions.Provenance(models.ProvenanceFile), "") require.NoError(t, err) + require.Len(t, store.Calls, 2) + + require.Equal(t, "Save", store.Calls[1].Method) + saved := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision) + assert.Equal(t, amConfigToken, saved.ConcurrencyToken) + assert.NotContains(t, saved.Config.TemplateFiles, templateName) + + prov.AssertCalled(t, "DeleteProvenance", mock.Anything, mock.MatchedBy(func(t *definitions.NotificationTemplate) bool { + return t.Name == templateName + }), orgID) + + prov.AssertExpectations(t) }) + }) - t.Run("succeeds when deleting from config file with no template section", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: defaultConfig, - }) - mockStore.EXPECT().SaveSucceeds() - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds() + t.Run("does not error when deleting templates that do not exist", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } - err := sut.DeleteTemplate(context.Background(), 1, "a", definitions.Provenance(models.ProvenanceAPI), "") + err := sut.DeleteTemplate(context.Background(), orgID, "not-found", definitions.Provenance(models.ProvenanceNone), "") - require.NoError(t, err) + require.NoError(t, err) + + prov.AssertExpectations(t) + }) + + t.Run("errors if provenance is not right", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) + + expectedErr := errors.New("test") + sut.validator = func(from, to models.Provenance) error { + assert.Equal(t, models.ProvenanceAPI, from) + assert.Equal(t, models.ProvenanceNone, to) + return expectedErr + } + + err := sut.DeleteTemplate(context.Background(), 1, templateName, definitions.Provenance(models.ProvenanceNone), "") + + require.ErrorIs(t, err, expectedErr) + + prov.AssertExpectations(t) + }) + + t.Run("errors if version is not right", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) + + err := sut.DeleteTemplate(context.Background(), 1, templateName, definitions.Provenance(models.ProvenanceNone), "bad-version") + + require.ErrorIs(t, err, ErrVersionConflict) + }) + + t.Run("propagates errors", func(t *testing.T) { + t.Run("when unable to read config", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + expectedErr := errors.New("test") + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return nil, expectedErr + } + + err := sut.DeleteTemplate(context.Background(), orgID, templateName, definitions.Provenance(models.ProvenanceNone), templateVersion) + + require.ErrorIs(t, err, expectedErr) + + prov.AssertExpectations(t) }) - t.Run("errors if provenance is not right", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) + t.Run("when reading provenance status fails", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + expectedErr := errors.New("test") + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, expectedErr) + + err := sut.DeleteTemplate(context.Background(), orgID, templateName, definitions.Provenance(models.ProvenanceNone), templateVersion) + + require.ErrorIs(t, err, expectedErr) + prov.AssertExpectations(t) + }) + + t.Run("when provenance fails to save", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() expectedErr := errors.New("test") - sut.validator = func(from, to models.Provenance) error { - assert.Equal(t, models.ProvenanceAPI, from) - assert.Equal(t, models.ProvenanceNone, to) - return expectedErr + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) + prov.EXPECT().DeleteProvenance(mock.Anything, mock.Anything, mock.Anything).Return(expectedErr) - err := sut.DeleteTemplate(context.Background(), 1, "a", definitions.Provenance(models.ProvenanceNone), "") + err := sut.DeleteTemplate(context.Background(), orgID, templateName, definitions.Provenance(models.ProvenanceNone), templateVersion) require.ErrorIs(t, err, expectedErr) + + prov.AssertExpectations(t) }) - t.Run("errors if version is not right", func(t *testing.T) { - mockStore := &legacy_storage.MockAMConfigStore{} - sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore)) - mockStore.EXPECT(). - GetsConfig(models.AlertConfiguration{ - AlertmanagerConfiguration: configWithTemplates, - }) - sut.provenanceStore.(*MockProvisioningStore).EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) + t.Run("when AM config fails to save", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + expectedErr := errors.New("test") + store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error { + return expectedErr + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - err := sut.DeleteTemplate(context.Background(), 1, "a", definitions.Provenance(models.ProvenanceNone), "bad-version") + err := sut.DeleteTemplate(context.Background(), orgID, templateName, definitions.Provenance(models.ProvenanceNone), templateVersion) - require.ErrorIs(t, err, ErrVersionConflict) + require.ErrorIs(t, err, expectedErr) }) }) } -func createTemplateServiceSut(configStore alertmanagerConfigStore) *TemplateService { +func createTemplateServiceSut() (*TemplateService, *legacy_storage.AlertmanagerConfigStoreFake, *MockProvisioningStore) { + store := &legacy_storage.AlertmanagerConfigStoreFake{} + provStore := &MockProvisioningStore{} return &TemplateService{ - configStore: configStore, - provenanceStore: &MockProvisioningStore{}, + configStore: store, + provenanceStore: provStore, xact: newNopTransactionManager(), log: log.NewNopLogger(), validator: validation.ValidateProvenanceRelaxed, - } + }, store, provStore } - -func createNotificationTemplate() definitions.NotificationTemplate { - return definitions.NotificationTemplate{ - Name: "test", - Template: "asdf", - } -} - -var defaultConfig = setting.GetAlertmanagerDefaultConfiguration() - -var configWithTemplates = ` -{ - "template_files": { - "a": "template" - }, - "alertmanager_config": { - "route": { - "receiver": "grafana-default-email" - }, - "receivers": [{ - "name": "grafana-default-email", - "grafana_managed_receiver_configs": [{ - "uid": "", - "name": "email receiver", - "type": "email", - "settings": { - "addresses": "" - } - }] - }] - } -} -` - -var brokenConfig = ` - "alertmanager_config": { - "route": { - "receiver": "grafana-default-email" - }, - "receivers": [{ - "name": "grafana-default-email", - "grafana_managed_receiver_configs": [{ - "uid": "abc", - "name": "default-email", - "type": "email", - "settings": {} - }] - }] - } -}` From 4b0e8653f2b31ea1b3237a6ef9b38f28e2bedb5d Mon Sep 17 00:00:00 2001 From: Kat Yang <69819079+yangkb09@users.noreply.github.com> Date: Thu, 15 Aug 2024 09:50:56 -0400 Subject: [PATCH 097/229] Refactor: Consolidate code for query generator (#91415) * Refactor: Consolidate code for query generator * Update public/app/features/trails/AutomaticMetricQueries/query-generators/default.ts Co-authored-by: ismail simsek * fix: rename index.ts to getQueryGeneratorFor.ts to avoid barrel files --------- Co-authored-by: ismail simsek --- .../AutomaticMetricQueries/AutoQueryEngine.ts | 2 +- .../query-generators/common/baseQuery.ts | 8 ++ .../query-generators/common/index.ts | 9 -- .../query-generators/common/queries.ts | 39 --------- .../query-generators/common/rules.ts | 39 --------- .../query-generators/common/types.ts | 5 -- .../queries.test.ts => default.test.ts} | 7 +- .../query-generators/default.ts | 82 +++++++++++++++++++ .../query-generators/getQueryGeneratorFor.ts | 20 +++++ .../query-generators/histogram.ts | 2 +- .../query-generators/index.ts | 13 --- .../query-generators/summary.ts | 6 +- .../query-generators/types.ts | 3 - 13 files changed, 119 insertions(+), 116 deletions(-) create mode 100644 public/app/features/trails/AutomaticMetricQueries/query-generators/common/baseQuery.ts delete mode 100644 public/app/features/trails/AutomaticMetricQueries/query-generators/common/index.ts delete mode 100644 public/app/features/trails/AutomaticMetricQueries/query-generators/common/queries.ts delete mode 100644 public/app/features/trails/AutomaticMetricQueries/query-generators/common/rules.ts delete mode 100644 public/app/features/trails/AutomaticMetricQueries/query-generators/common/types.ts rename public/app/features/trails/AutomaticMetricQueries/query-generators/{common/queries.test.ts => default.test.ts} (93%) create mode 100644 public/app/features/trails/AutomaticMetricQueries/query-generators/default.ts create mode 100644 public/app/features/trails/AutomaticMetricQueries/query-generators/getQueryGeneratorFor.ts delete mode 100644 public/app/features/trails/AutomaticMetricQueries/query-generators/index.ts delete mode 100644 public/app/features/trails/AutomaticMetricQueries/query-generators/types.ts diff --git a/public/app/features/trails/AutomaticMetricQueries/AutoQueryEngine.ts b/public/app/features/trails/AutomaticMetricQueries/AutoQueryEngine.ts index 822c388c925..94d2287e126 100644 --- a/public/app/features/trails/AutomaticMetricQueries/AutoQueryEngine.ts +++ b/public/app/features/trails/AutomaticMetricQueries/AutoQueryEngine.ts @@ -1,4 +1,4 @@ -import { getQueryGeneratorFor } from './query-generators'; +import { getQueryGeneratorFor } from './query-generators/getQueryGeneratorFor'; import { AutoQueryInfo } from './types'; export function getAutoQueriesForMetric(metric: string): AutoQueryInfo { diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/common/baseQuery.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/common/baseQuery.ts new file mode 100644 index 00000000000..d018b288a3a --- /dev/null +++ b/public/app/features/trails/AutomaticMetricQueries/query-generators/common/baseQuery.ts @@ -0,0 +1,8 @@ +import { VAR_METRIC_EXPR, VAR_FILTERS_EXPR } from 'app/features/trails/shared'; + +const GENERAL_BASE_QUERY = `${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}`; +const GENERAL_RATE_BASE_QUERY = `rate(${GENERAL_BASE_QUERY}[$__rate_interval])`; + +export function getGeneralBaseQuery(rate: boolean) { + return rate ? GENERAL_RATE_BASE_QUERY : GENERAL_BASE_QUERY; +} diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/common/index.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/common/index.ts deleted file mode 100644 index 4257e212ec7..00000000000 --- a/public/app/features/trails/AutomaticMetricQueries/query-generators/common/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { generateQueries } from './queries'; -import { getGeneratorParameters } from './rules'; - -function generator(metricParts: string[]) { - const params = getGeneratorParameters(metricParts); - return generateQueries(params); -} - -export default { generator }; diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/common/queries.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/common/queries.ts deleted file mode 100644 index eb43d8537ce..00000000000 --- a/public/app/features/trails/AutomaticMetricQueries/query-generators/common/queries.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { VAR_FILTERS_EXPR, VAR_GROUP_BY_EXP, VAR_METRIC_EXPR } from '../../../shared'; -import { AutoQueryInfo } from '../../types'; -import { generateCommonAutoQueryInfo } from '../common/generator'; - -import { AutoQueryParameters } from './types'; - -const GENERAL_BASE_QUERY = `${VAR_METRIC_EXPR}${VAR_FILTERS_EXPR}`; -const GENERAL_RATE_BASE_QUERY = `rate(${GENERAL_BASE_QUERY}[$__rate_interval])`; - -export function getGeneralBaseQuery(rate: boolean) { - return rate ? GENERAL_RATE_BASE_QUERY : GENERAL_BASE_QUERY; -} - -const aggLabels: Record = { - avg: 'average', - sum: 'overall', -}; - -function getAggLabel(agg: string) { - return aggLabels[agg] || agg; -} - -export function generateQueries({ agg, rate, unit }: AutoQueryParameters): AutoQueryInfo { - const baseQuery = getGeneralBaseQuery(rate); - - const aggregationDescription = rate ? `${getAggLabel(agg)} per-second rate` : `${getAggLabel(agg)}`; - - const description = `${VAR_METRIC_EXPR} (${aggregationDescription})`; - - const mainQueryExpr = `${agg}(${baseQuery})`; - const breakdownQueryExpr = `${agg}(${baseQuery})by(${VAR_GROUP_BY_EXP})`; - - return generateCommonAutoQueryInfo({ - description, - mainQueryExpr, - breakdownQueryExpr, - unit, - }); -} diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/common/rules.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/common/rules.ts deleted file mode 100644 index 96e0302089d..00000000000 --- a/public/app/features/trails/AutomaticMetricQueries/query-generators/common/rules.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { getUnit, getPerSecondRateUnit } from '../../units'; - -import { AutoQueryParameters } from './types'; - -/** These suffixes will set rate to true */ -const RATE_SUFFIXES = new Set(['count', 'total']); - -const UNSUPPORTED_SUFFIXES = new Set(['sum', 'bucket']); - -/** Non-default aggregattion keyed by suffix */ -const SPECIFIC_AGGREGATIONS_FOR_SUFFIX: Record = { - count: 'sum', - total: 'sum', -}; - -function checkPreviousForUnit(suffix: string) { - return suffix === 'total'; -} - -export function getGeneratorParameters(metricParts: string[]): AutoQueryParameters { - const suffix = metricParts.at(-1); - - if (suffix == null || UNSUPPORTED_SUFFIXES.has(suffix)) { - throw new Error(`This function does not support a metric suffix of "${suffix}"`); - } - - const rate = RATE_SUFFIXES.has(suffix); - const agg = SPECIFIC_AGGREGATIONS_FOR_SUFFIX[suffix] || 'avg'; - - const unitSuffix = checkPreviousForUnit(suffix) ? metricParts.at(-2) : suffix; - - const unit = rate ? getPerSecondRateUnit(unitSuffix) : getUnit(unitSuffix); - - return { - agg, - unit, - rate, - }; -} diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/common/types.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/common/types.ts deleted file mode 100644 index cf49ce8e526..00000000000 --- a/public/app/features/trails/AutomaticMetricQueries/query-generators/common/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type AutoQueryParameters = { - agg: string; - unit: string; - rate: boolean; -}; diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/common/queries.test.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/default.test.ts similarity index 93% rename from public/app/features/trails/AutomaticMetricQueries/query-generators/common/queries.test.ts rename to public/app/features/trails/AutomaticMetricQueries/query-generators/default.test.ts index bb8650338ed..cddc48925ec 100644 --- a/public/app/features/trails/AutomaticMetricQueries/query-generators/common/queries.test.ts +++ b/public/app/features/trails/AutomaticMetricQueries/query-generators/default.test.ts @@ -1,7 +1,8 @@ -import { VAR_GROUP_BY_EXP } from '../../../shared'; -import { AutoQueryDef, AutoQueryInfo } from '../../types'; +import { VAR_GROUP_BY_EXP } from '../../shared'; +import { AutoQueryDef, AutoQueryInfo } from '../types'; -import { generateQueries, getGeneralBaseQuery } from './queries'; +import { getGeneralBaseQuery } from './common/baseQuery'; +import { generateQueries } from './default'; describe('generateQueries', () => { const agg = 'sum'; diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/default.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/default.ts new file mode 100644 index 00000000000..b81feb30d62 --- /dev/null +++ b/public/app/features/trails/AutomaticMetricQueries/query-generators/default.ts @@ -0,0 +1,82 @@ +import { VAR_GROUP_BY_EXP, VAR_METRIC_EXPR } from 'app/features/trails/shared'; + +import { AutoQueryInfo } from '../types'; +import { getPerSecondRateUnit, getUnit } from '../units'; + +import { getGeneralBaseQuery } from './common/baseQuery'; +import { generateCommonAutoQueryInfo } from './common/generator'; + +/** These suffixes will set rate to true */ +const RATE_SUFFIXES = new Set(['count', 'total']); + +const UNSUPPORTED_SUFFIXES = new Set(['sum', 'bucket']); + +/** Non-default aggregation keyed by suffix */ +const SPECIFIC_AGGREGATIONS_FOR_SUFFIX: Record = { + count: 'sum', + total: 'sum', +}; + +function shouldCheckPreviousSuffixForUnit(suffix: string) { + return suffix === 'total'; +} + +const aggLabels: Record = { + avg: 'average', + sum: 'overall', +}; + +function getAggLabel(agg: string) { + return aggLabels[agg] || agg; +} + +export type AutoQueryParameters = { + agg: string; + unit: string; + rate: boolean; +}; + +export function generateQueries({ agg, rate, unit }: AutoQueryParameters): AutoQueryInfo { + const baseQuery = getGeneralBaseQuery(rate); + + const aggregationDescription = rate ? `${getAggLabel(agg)} per-second rate` : `${getAggLabel(agg)}`; + + const description = `${VAR_METRIC_EXPR} (${aggregationDescription})`; + + const mainQueryExpr = `${agg}(${baseQuery})`; + const breakdownQueryExpr = `${agg}(${baseQuery})by(${VAR_GROUP_BY_EXP})`; + + return generateCommonAutoQueryInfo({ + description, + mainQueryExpr, + breakdownQueryExpr, + unit, + }); +} + +export function createDefaultMetricQueryDefs(metricParts: string[]) { + // Get the last part of the metric name + const suffix = metricParts.at(-1); + + // If the suffix is null or is in the set of unsupported suffixes, throw an error because the metric should be delegated to a different generator (summary or histogram) + if (suffix == null || UNSUPPORTED_SUFFIXES.has(suffix)) { + throw new Error(`This function does not support a metric suffix of "${suffix}"`); + } + + // Check if generating rate query and/or aggregation query + const rate = RATE_SUFFIXES.has(suffix); + const agg = SPECIFIC_AGGREGATIONS_FOR_SUFFIX[suffix] || 'avg'; + + // Try to find the unit in the Prometheus metric name + const unitSuffix = shouldCheckPreviousSuffixForUnit(suffix) ? metricParts.at(-2) : suffix; + + // Get the Grafana unit or Grafana rate unit + const unit = rate ? getPerSecondRateUnit(unitSuffix) : getUnit(unitSuffix); + + const params = { + agg, + unit, + rate, + }; + return generateQueries(params); +} diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/getQueryGeneratorFor.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/getQueryGeneratorFor.ts new file mode 100644 index 00000000000..e44bfc31e60 --- /dev/null +++ b/public/app/features/trails/AutomaticMetricQueries/query-generators/getQueryGeneratorFor.ts @@ -0,0 +1,20 @@ +import { AutoQueryInfo } from '../types'; + +import { createDefaultMetricQueryDefs } from './default'; +import { createHistogramMetricQueryDefs } from './histogram'; +import { createSummaryMetricQueryDefs } from './summary'; + +// TODO: when we have a known unit parameter, use that rather than having the generator functions infer from suffix +export type MetricQueriesGenerator = (metricParts: string[]) => AutoQueryInfo; + +export function getQueryGeneratorFor(suffix?: string): MetricQueriesGenerator { + if (suffix === 'sum') { + return createSummaryMetricQueryDefs; + } + + if (suffix === 'bucket') { + return createHistogramMetricQueryDefs; + } + + return createDefaultMetricQueryDefs; +} diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/histogram.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/histogram.ts index e6e4644bb15..2a12b285ad7 100644 --- a/public/app/features/trails/AutomaticMetricQueries/query-generators/histogram.ts +++ b/public/app/features/trails/AutomaticMetricQueries/query-generators/histogram.ts @@ -7,7 +7,7 @@ import { simpleGraphBuilder } from '../graph-builders/simple'; import { AutoQueryDef } from '../types'; import { getUnit } from '../units'; -export function createHistogramQueryDefs(metricParts: string[]) { +export function createHistogramMetricQueryDefs(metricParts: string[]) { const title = `${VAR_METRIC_EXPR}`; const unitSuffix = metricParts.at(-2); diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/index.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/index.ts deleted file mode 100644 index 569f4e8e0bf..00000000000 --- a/public/app/features/trails/AutomaticMetricQueries/query-generators/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import general from './common'; -import { createHistogramQueryDefs } from './histogram'; -import { createSummaryQueryDefs } from './summary'; -import { MetricQueriesGenerator } from './types'; - -const SUFFIX_TO_ALTERNATIVE_GENERATOR: Record = { - sum: createSummaryQueryDefs, - bucket: createHistogramQueryDefs, -}; - -export function getQueryGeneratorFor(suffix?: string) { - return (suffix && SUFFIX_TO_ALTERNATIVE_GENERATOR[suffix]) || general.generator; -} diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/summary.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/summary.ts index b6a625b0898..cd73d93db25 100644 --- a/public/app/features/trails/AutomaticMetricQueries/query-generators/summary.ts +++ b/public/app/features/trails/AutomaticMetricQueries/query-generators/summary.ts @@ -2,13 +2,13 @@ import { VAR_GROUP_BY_EXP, VAR_METRIC_EXPR } from '../../shared'; import { AutoQueryInfo } from '../types'; import { getUnit } from '../units'; +import { getGeneralBaseQuery } from './common/baseQuery'; import { generateCommonAutoQueryInfo } from './common/generator'; -import { getGeneralBaseQuery } from './common/queries'; -export function createSummaryQueryDefs(metricParts: string[]): AutoQueryInfo { +export function createSummaryMetricQueryDefs(metricParts: string[]): AutoQueryInfo { const suffix = metricParts.at(-1); if (suffix !== 'sum') { - throw new Error('createSummaryQueryDefs is only to be used for metrics that end in "_sum"'); + throw new Error('createSummaryMetricQueryDefs is only to be used for metrics that end in "_sum"'); } const unitSuffix = metricParts.at(-2); diff --git a/public/app/features/trails/AutomaticMetricQueries/query-generators/types.ts b/public/app/features/trails/AutomaticMetricQueries/query-generators/types.ts deleted file mode 100644 index ddf30ccc27c..00000000000 --- a/public/app/features/trails/AutomaticMetricQueries/query-generators/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AutoQueryInfo } from '../types'; - -export type MetricQueriesGenerator = (metricParts: string[]) => AutoQueryInfo; From aaf33c792325535733cd7526b068889a1ceb4cd9 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 15 Aug 2024 16:13:27 +0200 Subject: [PATCH 098/229] Zanzana: Migrate basic, fixed and custom roles (#91814) * Zanzana: Migrate basic roles permissions * add basic roles assignments * refactor * Sync basic roles permissions in all orgs * migrate fixed roles * map root folders to orgs * fix basic role assignments in orgs * migrate other roles * migrate team roles assignments * add notes about authorization schema * don't migrate fixed roles --- .../accesscontrol/migrator/zanzana.go | 305 +++++++++++++++++- pkg/services/authz/zanzana/schema/README.md | 110 +++++++ pkg/services/authz/zanzana/schema/schema.fga | 1 - pkg/services/authz/zanzana/translations.go | 51 ++- pkg/services/authz/zanzana/zanzana.go | 49 ++- 5 files changed, 504 insertions(+), 12 deletions(-) create mode 100644 pkg/services/authz/zanzana/schema/README.md diff --git a/pkg/services/accesscontrol/migrator/zanzana.go b/pkg/services/accesscontrol/migrator/zanzana.go index 862cb1caf74..437525e6724 100644 --- a/pkg/services/accesscontrol/migrator/zanzana.go +++ b/pkg/services/accesscontrol/migrator/zanzana.go @@ -3,6 +3,7 @@ package migrator import ( "context" "fmt" + "slices" "strconv" "strings" @@ -36,6 +37,11 @@ func NewZanzanaSynchroniser(client zanzana.Client, store db.DB, collectors ...Tu managedPermissionsCollector(store), folderTreeCollector(store), dashboardFolderCollector(store), + basicRolesCollector(store), + customRolesCollector(store), + basicRoleAssignemtCollector(store), + userRoleAssignemtCollector(store), + teamRoleAssignemtCollector(store), ) return &ZanzanaSynchroniser{ @@ -187,7 +193,7 @@ func folderTreeCollector(store db.DB) TupleCollector { return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { const collectorID = "folder" const query = ` - SELECT uid, parent_uid, org_id FROM folder WHERE parent_uid IS NOT NULL + SELECT uid, parent_uid, org_id FROM folder ` type folder struct { OrgID int64 `xorm:"org_id"` @@ -205,12 +211,21 @@ func folderTreeCollector(store db.DB) TupleCollector { } for _, f := range folders { - tuple := &openfgav1.TupleKey{ - User: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, f.ParentUID, "", strconv.FormatInt(f.OrgID, 10)), - Object: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, f.FolderUID, "", strconv.FormatInt(f.OrgID, 10)), - Relation: zanzana.RelationParent, + var tuple *openfgav1.TupleKey + if f.ParentUID != "" { + tuple = &openfgav1.TupleKey{ + Object: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, f.FolderUID, "", strconv.FormatInt(f.OrgID, 10)), + Relation: zanzana.RelationParent, + User: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, f.ParentUID, "", strconv.FormatInt(f.OrgID, 10)), + } + } else { + // Map root folders to org + tuple = &openfgav1.TupleKey{ + Object: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, f.FolderUID, "", strconv.FormatInt(f.OrgID, 10)), + Relation: zanzana.RelationOrg, + User: zanzana.NewTupleEntry(zanzana.TypeOrg, strconv.FormatInt(f.OrgID, 10), ""), + } } - tuples[collectorID] = append(tuples[collectorID], tuple) } @@ -253,3 +268,281 @@ func dashboardFolderCollector(store db.DB) TupleCollector { return nil } } + +// basicRolesCollector migrates basic roles to OpenFGA tuples +func basicRolesCollector(store db.DB) TupleCollector { + return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { + const collectorID = "basic_role" + const query = ` + SELECT r.name, r.uid as role_uid, p.action, p.kind, p.identifier, r.org_id + FROM permission p + INNER JOIN role r ON p.role_id = r.id + LEFT JOIN builtin_role br ON r.id = br.role_id + WHERE r.name LIKE 'basic:%' + ` + type Permission struct { + RoleName string `xorm:"role_name"` + OrgID int64 `xorm:"org_id"` + Action string `xorm:"action"` + Kind string + Identifier string + RoleUID string `xorm:"role_uid"` + } + + var permissions []Permission + err := store.WithDbSession(ctx, func(sess *db.Session) error { + return sess.SQL(query).Find(&permissions) + }) + if err != nil { + return err + } + + for _, p := range permissions { + type Org struct { + Id int64 + Name string + } + var orgs []Org + orgsQuery := "SELECT id, name FROM org" + err := store.WithDbSession(ctx, func(sess *db.Session) error { + return sess.SQL(orgsQuery).Find(&orgs) + }) + if err != nil { + return err + } + + // Populate basic roles permissions for every org + for _, org := range orgs { + var subject string + if p.RoleUID != "" { + subject = zanzana.NewScopedTupleEntry(zanzana.TypeRole, p.RoleUID, "assignee", strconv.FormatInt(org.Id, 10)) + } else { + continue + } + + var tuple *openfgav1.TupleKey + ok := false + if p.Identifier == "" || p.Identifier == "*" { + tuple, ok = zanzana.TranslateToOrgTuple(subject, p.Action, org.Id) + } else { + tuple, ok = zanzana.TranslateToTuple(subject, p.Action, p.Kind, p.Identifier, org.Id) + } + if !ok { + continue + } + + key := fmt.Sprintf("%s-%s", collectorID, p.Action) + if !slices.ContainsFunc(tuples[key], func(e *openfgav1.TupleKey) bool { + // skip duplicated tuples + return e.Object == tuple.Object && e.Relation == tuple.Relation && e.User == tuple.User + }) { + tuples[key] = append(tuples[key], tuple) + } + } + } + + return nil + } +} + +// customRolesCollector migrates custom roles to OpenFGA tuples +func customRolesCollector(store db.DB) TupleCollector { + return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { + const collectorID = "custom_role" + const query = ` + SELECT r.name, r.uid as role_uid, p.action, p.kind, p.identifier, r.org_id + FROM permission p + INNER JOIN role r ON p.role_id = r.id + LEFT JOIN builtin_role br ON r.id = br.role_id + WHERE r.name NOT LIKE 'basic:%' + AND r.name NOT LIKE 'fixed:%' + AND r.name NOT LIKE 'managed:%' + ` + type Permission struct { + RoleName string `xorm:"role_name"` + OrgID int64 `xorm:"org_id"` + Action string `xorm:"action"` + Kind string + Identifier string + RoleUID string `xorm:"role_uid"` + } + + var permissions []Permission + err := store.WithDbSession(ctx, func(sess *db.Session) error { + return sess.SQL(query).Find(&permissions) + }) + if err != nil { + return err + } + + for _, p := range permissions { + var subject string + if p.RoleUID != "" { + subject = zanzana.NewScopedTupleEntry(zanzana.TypeRole, p.RoleUID, "assignee", strconv.FormatInt(p.OrgID, 10)) + } else { + continue + } + + var tuple *openfgav1.TupleKey + ok := false + if p.Identifier == "" || p.Identifier == "*" { + tuple, ok = zanzana.TranslateToOrgTuple(subject, p.Action, p.OrgID) + } else { + tuple, ok = zanzana.TranslateToTuple(subject, p.Action, p.Kind, p.Identifier, p.OrgID) + } + if !ok { + continue + } + + key := fmt.Sprintf("%s-%s", collectorID, p.Action) + if !slices.ContainsFunc(tuples[key], func(e *openfgav1.TupleKey) bool { + // skip duplicated tuples + return e.Object == tuple.Object && e.Relation == tuple.Relation && e.User == tuple.User + }) { + tuples[key] = append(tuples[key], tuple) + } + } + + return nil + } +} + +func basicRoleAssignemtCollector(store db.DB) TupleCollector { + return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { + const collectorID = "basic_role_assignment" + const query = ` + SELECT ou.org_id, u.uid as user_uid, ou.role as org_role, u.is_admin + FROM org_user ou + LEFT JOIN user u ON u.id = ou.user_id + ` + type Assignment struct { + OrgID int64 `xorm:"org_id"` + UserUID string `xorm:"user_uid"` + OrgRole string `xorm:"org_role"` + IsAdmin bool `xorm:"is_admin"` + } + + var assignments []Assignment + err := store.WithDbSession(ctx, func(sess *db.Session) error { + return sess.SQL(query).Find(&assignments) + }) + + if err != nil { + return err + } + + for _, a := range assignments { + var subject string + if a.UserUID != "" && a.OrgRole != "" { + subject = zanzana.NewTupleEntry(zanzana.TypeUser, a.UserUID, "") + } else { + continue + } + + roleUID := zanzana.TranslateBasicRole(a.OrgRole) + + tuple := &openfgav1.TupleKey{ + User: subject, + Relation: zanzana.RelationAssignee, + Object: zanzana.NewScopedTupleEntry(zanzana.TypeRole, roleUID, "", strconv.FormatInt(a.OrgID, 10)), + } + + key := fmt.Sprintf("%s-%s", collectorID, zanzana.RelationAssignee) + tuples[key] = append(tuples[key], tuple) + } + + return nil + } +} + +func userRoleAssignemtCollector(store db.DB) TupleCollector { + return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { + const collectorID = "user_role_assignment" + const query = ` + SELECT ur.org_id, u.uid AS user_uid, r.uid AS role_uid + FROM user_role ur + LEFT JOIN role r ON r.id = ur.role_id + LEFT JOIN user u ON u.id = ur.user_id + ` + + type Assignment struct { + OrgID int64 `xorm:"org_id"` + UserUID string `xorm:"user_uid"` + RoleUID string `xorm:"role_uid"` + } + + var assignments []Assignment + err := store.WithDbSession(ctx, func(sess *db.Session) error { + return sess.SQL(query).Find(&assignments) + }) + if err != nil { + return err + } + + for _, a := range assignments { + var subject string + if a.UserUID != "" && a.RoleUID != "" { + subject = zanzana.NewTupleEntry(zanzana.TypeUser, a.UserUID, "") + } else { + continue + } + + tuple := &openfgav1.TupleKey{ + User: subject, + Relation: zanzana.RelationAssignee, + Object: zanzana.NewScopedTupleEntry(zanzana.TypeRole, a.RoleUID, "", strconv.FormatInt(a.OrgID, 10)), + } + + key := fmt.Sprintf("%s-%s", collectorID, zanzana.RelationAssignee) + tuples[key] = append(tuples[key], tuple) + } + + return nil + } +} + +func teamRoleAssignemtCollector(store db.DB) TupleCollector { + return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { + const collectorID = "team_role_assignment" + const query = ` + SELECT tr.org_id, t.uid AS team_uid, r.uid AS role_uid + FROM team_role tr + LEFT JOIN role r ON r.id = tr.role_id + LEFT JOIN team t ON t.id = tr.team_id + ` + + type Assignment struct { + OrgID int64 `xorm:"org_id"` + TeamUID string `xorm:"team_uid"` + RoleUID string `xorm:"role_uid"` + } + + var assignments []Assignment + err := store.WithDbSession(ctx, func(sess *db.Session) error { + return sess.SQL(query).Find(&assignments) + }) + if err != nil { + return err + } + + for _, a := range assignments { + var subject string + if a.TeamUID != "" && a.RoleUID != "" { + subject = zanzana.NewTupleEntry(zanzana.TypeTeam, a.TeamUID, "member") + } else { + continue + } + + tuple := &openfgav1.TupleKey{ + User: subject, + Relation: zanzana.RelationAssignee, + Object: zanzana.NewScopedTupleEntry(zanzana.TypeRole, a.RoleUID, "", strconv.FormatInt(a.OrgID, 10)), + } + + key := fmt.Sprintf("%s-%s", collectorID, zanzana.RelationAssignee) + tuples[key] = append(tuples[key], tuple) + } + + return nil + } +} diff --git a/pkg/services/authz/zanzana/schema/README.md b/pkg/services/authz/zanzana/schema/README.md new file mode 100644 index 00000000000..63fc4bfa013 --- /dev/null +++ b/pkg/services/authz/zanzana/schema/README.md @@ -0,0 +1,110 @@ +# Authorization schema + +Here's some notes about [OpenFGA authorization model](https://openfga.dev/docs/modeling/getting-started) (schema) using to model access control in Grafana. + +## Org-level permissions + +Most of the permissions are exist in org. Users, teams, dashboards, folders and other objects also related to specific org. + +## Dashboards and folders + +Folder hierarchy is stored directly in OpenFGA database. Each dashboard has parent folder and every folder could have sub-folders. Root-level folders do not have parents, but instead, they related to specific org: + +```text +type org + relations + define instance: [instance] + define member: [user] + +type folder + relations + define parent: [folder] + define org: [org] + +type dashboard + relations + define org: [org] + define parent: [folder] +``` + +Therefore, folders tree is stored as tuples like this: + +```text +folder:- parent dashboard:- +folder:- parent folder:- +org: org folder:- +``` + +## Managed permissions + +In the RBAC model managed permissions stored as a special "managed" role permissions. OpenFGA model allows to assign permissions directly to users, so it produces following tuples: + +```text +user: read folder:- +``` + +It's also possible to assign permissions for team members using `#member` relation: + +```text +team:#member read folder:- +``` + +It's important to understand that folder permissions cannot be directly assigned to teams, because it's restricted by schema: + +```text +type folder + relations + define parent: [folder] + define org: [org] + + define read: [user, team#member, role#assignee] or read from parent or folder_read from org + +type team + relations + define org: [org] + define admin: [user] + define member: [user] or admin +``` + +Therefore, `team#member` can have `read` relation to folder and user will be automatically granted the same permission if it has `member` relation to specific team. + +## Roles and role assignments + +RBAC authorization model grants permissions to users through roles and role assignments. All permissions are linked to roles and then roles granted to users. To model this in OpenFGA, we use org-level permission and `role` type. + +To understand how RBAC permissions linked to roles, let's take a look at the dashboard read permission as example: + +```text +type org + relations + define instance: [instance] + define member: [user] + + define folder_read: [role#assignee] + +type role + relations + define org: [org] + define assignee: [user, team#member, role#assignee] + +type folder + relations + define parent: [folder] + define org: [org] + + define read: [user, team#member, role#assignee] or read from parent or folder_read from org +``` + +According to the schema, user can get `read` access to dashboard if it has `read` relation granted directly to the dashboard ot its parent folders, or by having `folder_read from org`. If we take a look at `folder_read` definition in the org type, we could see that this relation could be granted to `role#assignee`. So in order to allow user to read all dahboards in org, following tuples should be added: + +```text +role:-#assignee folder_read org: +user: assignee role: +``` + +In case of `Admin` basic role, it will be looking like: + +```text +role:1-basic_admin#assignee folder_read org:1 +user:admin assignee role:1-basic_admin +``` diff --git a/pkg/services/authz/zanzana/schema/schema.fga b/pkg/services/authz/zanzana/schema/schema.fga index d4d8a5ba8df..02cc33b4407 100644 --- a/pkg/services/authz/zanzana/schema/schema.fga +++ b/pkg/services/authz/zanzana/schema/schema.fga @@ -9,7 +9,6 @@ type org relations define instance: [instance] define member: [user] - define viewer: [user] # team management define team_create: [role#assignee] diff --git a/pkg/services/authz/zanzana/translations.go b/pkg/services/authz/zanzana/translations.go index 8e52ea2c69c..c851547e973 100644 --- a/pkg/services/authz/zanzana/translations.go +++ b/pkg/services/authz/zanzana/translations.go @@ -46,16 +46,59 @@ var dashboardActions = map[string]string{ "dashboards.permissions:write": "permissions_write", } +var orgActions = map[string]string{ + "folders:create": "folder_create", + "folders:read": "folder_read", + "folders:write": "folder_write", + "folders:delete": "folder_delete", + "folders.permissions:read": "folder_permissions_read", + "folders.permissions:write": "folder_permissions_write", + + "dashboards:create": "dashboard_create", + "dashboards:read": "dashboard_read", + "dashboards:write": "dashboard_write", + "dashboards:delete": "dashboard_delete", + "dashboards.permissions:read": "dashboard_permissions_read", + "dashboards.permissions:write": "dashboard_permissions_write", + + "library.panels:create": "library_panel_create", + "library.panels:read": "library_panel_read", + "library.panels:write": "library_panel_write", + "library.panels:delete": "library_panel_delete", + + "alert.rules:create": "alert_rule_create", + "alert.rules:read": "alert_rule_read", + "alert.rules:write": "alert_rule_write", + "alert.rules:delete": "alert_rule_delete", + + "alert.silences:create": "alert_silence_create", + "alert.silences:read": "alert_silence_read", + "alert.silences:write": "alert_silence_write", +} + // RBAC to OpenFGA translations grouped by kind var actionKindTranslations = map[string]actionKindTranslation{ - "folders": { - objectType: "folder", + KindOrg: { + objectType: TypeOrg, + orgScoped: false, + translations: orgActions, + }, + KindFolders: { + objectType: TypeFolder, orgScoped: true, translations: folderActions, }, - "dashboards": { - objectType: "dashboard", + KindDashboards: { + objectType: TypeDashboard, orgScoped: true, translations: dashboardActions, }, } + +var basicRolesTranslations = map[string]string{ + RoleGrafanaAdmin: "basic_grafana_admin", + RoleAdmin: "basic_admin", + RoleEditor: "basic_editor", + RoleViewer: "basic_viewer", + RoleNone: "basic_none", +} diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go index 940c9c3691f..d59b7616e73 100644 --- a/pkg/services/authz/zanzana/zanzana.go +++ b/pkg/services/authz/zanzana/zanzana.go @@ -10,14 +10,37 @@ import ( const ( TypeUser string = "user" TypeTeam string = "team" + TypeRole string = "role" TypeFolder string = "folder" TypeDashboard string = "dashboard" + TypeOrg string = "org" ) const ( RelationTeamMember string = "member" RelationTeamAdmin string = "admin" RelationParent string = "parent" + RelationAssignee string = "assignee" + RelationOrg string = "org" +) + +const ( + KindOrg string = "org" + KindDashboards string = "dashboards" + KindFolders string = "folders" +) + +const ( + RoleGrafanaAdmin = "Grafana Admin" + RoleAdmin = "Admin" + RoleEditor = "Editor" + RoleViewer = "Viewer" + RoleNone = "None" + + BasicRolePrefix = "basic:" + BasicRoleUIDPrefix = "basic_" + + GlobalOrgID = 0 ) // NewTupleEntry constructs new openfga entry type:id[#relation]. @@ -34,7 +57,7 @@ func NewTupleEntry(objectType, id, relation string) string { // NewScopedTupleEntry constructs new openfga entry type:id[#relation] // with id prefixed by scope (usually org id) func NewScopedTupleEntry(objectType, id, relation, scope string) string { - return NewTupleEntry(objectType, fmt.Sprintf("%s-%s", scope, id), "") + return NewTupleEntry(objectType, fmt.Sprintf("%s-%s", scope, id), relation) } func TranslateToTuple(user string, action, kind, identifier string, orgID int64) (*openfgav1.TupleKey, bool) { @@ -64,3 +87,27 @@ func TranslateToTuple(user string, action, kind, identifier string, orgID int64) return tuple, true } + +func TranslateToOrgTuple(user string, action string, orgID int64) (*openfgav1.TupleKey, bool) { + typeTranslation, ok := actionKindTranslations[KindOrg] + if !ok { + return nil, false + } + + relation, ok := typeTranslation.translations[action] + if !ok { + return nil, false + } + + tuple := &openfgav1.TupleKey{ + Relation: relation, + User: user, + Object: NewTupleEntry(typeTranslation.objectType, strconv.FormatInt(orgID, 10), ""), + } + + return tuple, true +} + +func TranslateBasicRole(role string) string { + return basicRolesTranslations[role] +} From f14fd5828a978407eec478113a67455d072206c3 Mon Sep 17 00:00:00 2001 From: Kyle Cunningham Date: Thu, 15 Aug 2024 10:04:36 -0500 Subject: [PATCH 099/229] Table panel: Improve cell inspector (#91862) * Improve cell inspector * Update types * Prettier * Type checking fixes --- .../src/components/Monaco/CodeEditor.tsx | 5 +- .../grafana-ui/src/components/Monaco/types.ts | 1 + .../src/components/Table/CellActions.tsx | 4 +- .../src/components/Table/DefaultCell.tsx | 5 +- .../src/components/Table/JSONViewCell.tsx | 3 +- .../components/Table/TableCellInspector.tsx | 81 ++++++++++++------- 6 files changed, 66 insertions(+), 33 deletions(-) diff --git a/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx b/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx index ae959975bbb..00aaf6b4533 100644 --- a/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx +++ b/packages/grafana-ui/src/components/Monaco/CodeEditor.tsx @@ -129,7 +129,8 @@ class UnthemedCodeEditor extends PureComponent { }; render() { - const { theme, language, width, height, showMiniMap, showLineNumbers, readOnly, monacoOptions } = this.props; + const { theme, language, width, height, showMiniMap, showLineNumbers, readOnly, wordWrap, monacoOptions } = + this.props; const { alwaysConsumeMouseWheel, ...restMonacoOptions } = monacoOptions ?? {}; const value = this.props.value ?? ''; @@ -138,7 +139,7 @@ class UnthemedCodeEditor extends PureComponent { const containerStyles = this.props.containerStyles ?? getStyles(theme).container; const options: MonacoOptions = { - wordWrap: 'off', + wordWrap: wordWrap ? 'on' : 'off', tabSize: 2, codeLens: false, contextmenu: false, diff --git a/packages/grafana-ui/src/components/Monaco/types.ts b/packages/grafana-ui/src/components/Monaco/types.ts index d41fcbe13d3..0d403c0ca24 100644 --- a/packages/grafana-ui/src/components/Monaco/types.ts +++ b/packages/grafana-ui/src/components/Monaco/types.ts @@ -27,6 +27,7 @@ export interface CodeEditorProps { readOnly?: boolean; showMiniMap?: boolean; showLineNumbers?: boolean; + wordWrap?: boolean; monacoOptions?: MonacoOptions; /** diff --git a/packages/grafana-ui/src/components/Table/CellActions.tsx b/packages/grafana-ui/src/components/Table/CellActions.tsx index e5908664428..222c441d5e2 100644 --- a/packages/grafana-ui/src/components/Table/CellActions.tsx +++ b/packages/grafana-ui/src/components/Table/CellActions.tsx @@ -6,12 +6,12 @@ import { IconButton } from '../IconButton/IconButton'; import { Stack } from '../Layout/Stack/Stack'; import { TooltipPlacement } from '../Tooltip'; -import { TableCellInspector } from './TableCellInspector'; +import { TableCellInspector, TableCellInspectorMode } from './TableCellInspector'; import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR, TableCellProps } from './types'; import { getTextAlign } from './utils'; interface CellActionProps extends TableCellProps { - previewMode: 'text' | 'code'; + previewMode: TableCellInspectorMode; } interface CommonButtonProps { diff --git a/packages/grafana-ui/src/components/Table/DefaultCell.tsx b/packages/grafana-ui/src/components/Table/DefaultCell.tsx index 02b03f10ea3..77a30a7c051 100644 --- a/packages/grafana-ui/src/components/Table/DefaultCell.tsx +++ b/packages/grafana-ui/src/components/Table/DefaultCell.tsx @@ -11,6 +11,7 @@ import { clearLinkButtonStyles } from '../Button'; import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu'; import { CellActions } from './CellActions'; +import { TableCellInspectorMode } from './TableCellInspector'; import { TableStyles } from './styles'; import { TableCellProps, CustomCellRendererProps, TableCellOptions } from './types'; import { getCellColors, getCellOptions } from './utils'; @@ -114,7 +115,9 @@ export const DefaultCell = (props: TableCellProps) => { )} - {hover && showActions && } + {hover && showActions && ( + + )}
); }; diff --git a/packages/grafana-ui/src/components/Table/JSONViewCell.tsx b/packages/grafana-ui/src/components/Table/JSONViewCell.tsx index ba5691b2c7e..d013d6dc6ad 100644 --- a/packages/grafana-ui/src/components/Table/JSONViewCell.tsx +++ b/packages/grafana-ui/src/components/Table/JSONViewCell.tsx @@ -7,6 +7,7 @@ import { Button, clearLinkButtonStyles } from '../Button'; import { DataLinksContextMenu } from '../DataLinks/DataLinksContextMenu'; import { CellActions } from './CellActions'; +import { TableCellInspectorMode } from './TableCellInspector'; import { TableCellProps } from './types'; export function JSONViewCell(props: TableCellProps): JSX.Element { @@ -51,7 +52,7 @@ export function JSONViewCell(props: TableCellProps): JSX.Element { )}
- {inspectEnabled && } + {inspectEnabled && }
); } diff --git a/packages/grafana-ui/src/components/Table/TableCellInspector.tsx b/packages/grafana-ui/src/components/Table/TableCellInspector.tsx index 7cc49f706b6..7f318608705 100644 --- a/packages/grafana-ui/src/components/Table/TableCellInspector.tsx +++ b/packages/grafana-ui/src/components/Table/TableCellInspector.tsx @@ -1,57 +1,84 @@ import { isString } from 'lodash'; +import { useState } from 'react'; import { ClipboardButton } from '../ClipboardButton/ClipboardButton'; import { Drawer } from '../Drawer/Drawer'; +import { Stack } from '../Layout/Stack/Stack'; import { CodeEditor } from '../Monaco/CodeEditor'; +import { Tab, TabsBar } from '../Tabs'; + +export enum TableCellInspectorMode { + code = 'code', + text = 'text', +} interface TableCellInspectorProps { value: any; onDismiss: () => void; - mode: 'code' | 'text'; + mode: TableCellInspectorMode; } export function TableCellInspector({ value, onDismiss, mode }: TableCellInspectorProps) { let displayValue = value; + const [currentMode, setMode] = useState(mode); + if (isString(value)) { const trimmedValue = value.trim(); // Exclude numeric strings like '123' from being displayed in code/JSON mode if (trimmedValue[0] === '{' || trimmedValue[0] === '[' || mode === 'code') { try { value = JSON.parse(value); - mode = 'code'; - } catch { - mode = 'text'; - } // ignore errors - } else { - mode = 'text'; + } catch {} } } else { displayValue = JSON.stringify(value, null, ' '); } let text = displayValue; - if (mode === 'code') { - text = JSON.stringify(value, null, ' '); - } + const tabs = [ + { + label: 'Plain text', + value: 'text', + }, + { + label: 'Code editor', + value: 'code', + }, + ]; + + const changeTabs = () => { + setMode(currentMode === TableCellInspectorMode.text ? TableCellInspectorMode.code : TableCellInspectorMode.text); + }; + + const tabBar = ( + + {tabs.map((t, index) => ( + + ))} + + ); return ( - - {mode === 'code' ? ( - 100} - value={text} - readOnly={true} - /> - ) : ( -
{text}
- )} - text}> - Copy to Clipboard - + + + text} style={{ marginLeft: 'auto', width: '200px' }}> + Copy to Clipboard + + {currentMode === 'code' ? ( + 100} + value={text} + readOnly={true} + wordWrap={true} + /> + ) : ( +
{text}
+ )} +
); } From f852bf684a625b44f82f7bf531336032937a195b Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 15 Aug 2024 17:14:55 +0200 Subject: [PATCH 100/229] Alerting: Fix duplicated silences in remote primary mode bug (#91902) * Alerting: Fix duplicated silences in remote primary mode bug * test that a new silence id returned by calling CreateSilence() on the internal Alertmanager is ignored --- .../remote/forked_alertmanager_test.go | 27 +++++++++++++++++++ .../remote_primary_forked_alertmanager.go | 21 ++++++++++++--- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/pkg/services/ngalert/remote/forked_alertmanager_test.go b/pkg/services/ngalert/remote/forked_alertmanager_test.go index 63221c45daa..e78a0561dea 100644 --- a/pkg/services/ngalert/remote/forked_alertmanager_test.go +++ b/pkg/services/ngalert/remote/forked_alertmanager_test.go @@ -3,6 +3,7 @@ package remote import ( "context" "errors" + "fmt" "testing" "time" @@ -474,6 +475,32 @@ func TestForkedAlertmanager_ModeRemotePrimary(t *testing.T) { id, err = forked.CreateSilence(ctx, testSilence) require.NoError(tt, err) require.Equal(tt, expID, id) + + // If the silence ID changes, the internal Alertmanager should attempt to expire the old silence. + newID := "new" + internal, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().CreateSilence(mock.Anything, mock.Anything).Return(newID, nil).Once() + internal.EXPECT().DeleteSilence(mock.Anything, mock.Anything).Return(nil).Once() + // If internal.CreateSilence() returns a new id, it should be ignored. + internal.EXPECT().CreateSilence(mock.Anything, mock.Anything).Return("random-id", nil).Once() + id, err = forked.CreateSilence(ctx, testSilence) + require.NoError(tt, err) + require.Equal(tt, newID, testSilence.ID) + require.Equal(tt, newID, id) + + // Restore original ID. + testSilence.ID = expID + + // An error attempting to delete a silence in the internal Alertmanager not be returned. + internal, remote, forked = genTestAlertmanagers(tt, modeRemotePrimary) + remote.EXPECT().CreateSilence(mock.Anything, mock.Anything).Return(newID, nil).Once() + internal.EXPECT().DeleteSilence(mock.Anything, mock.Anything).Return(fmt.Errorf("test error")).Once() + // If internal.CreateSilence() returns a new id, it should be ignored. + internal.EXPECT().CreateSilence(mock.Anything, mock.Anything).Return("random-id", nil).Once() + id, err = forked.CreateSilence(ctx, testSilence) + require.NoError(tt, err) + require.Equal(tt, newID, testSilence.ID) + require.Equal(tt, newID, id) }) t.Run("DeleteSilence", func(tt *testing.T) { diff --git a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go index be783deef4e..1577f3a9470 100644 --- a/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go +++ b/pkg/services/ngalert/remote/remote_primary_forked_alertmanager.go @@ -2,6 +2,7 @@ package remote import ( "context" + "errors" "fmt" alertingNotify "github.com/grafana/alerting/notify" @@ -72,16 +73,30 @@ func (fam *RemotePrimaryForkedAlertmanager) GetStatus(ctx context.Context) (apim } func (fam *RemotePrimaryForkedAlertmanager) CreateSilence(ctx context.Context, silence *apimodels.PostableSilence) (string, error) { - uid, err := fam.remote.CreateSilence(ctx, silence) + originalID := silence.ID + id, err := fam.remote.CreateSilence(ctx, silence) if err != nil { return "", err } - silence.ID = uid + if originalID != "" && originalID != id { + // ID has changed, expire the old silence before creating a new one. + if err := fam.internal.DeleteSilence(ctx, originalID); err != nil { + if errors.Is(err, alertingNotify.ErrSilenceNotFound) { + // This can happen if the silence was created in the remote AM without using the Grafana UI + // in remote primary mode, or if the silence failed to be replicated in the internal AM. + fam.log.Warn("Failed to delete silence in the internal Alertmanager", "err", err, "id", originalID) + } else { + fam.log.Error("Failed to delete silence in the internal Alertmanager", "err", err, "id", originalID) + } + } + } + + silence.ID = id if _, err := fam.internal.CreateSilence(ctx, silence); err != nil { fam.log.Error("Error creating silence in the internal Alertmanager", "err", err, "silence", silence) } - return uid, nil + return id, nil } func (fam *RemotePrimaryForkedAlertmanager) DeleteSilence(ctx context.Context, id string) error { From 40c6f741c08868f1fb42b5b5760faf1e5ef93b52 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Thu, 15 Aug 2024 15:17:47 +0000 Subject: [PATCH 101/229] Logs: Show older logs button when infinite scroll is enabled and sort order is descending (#91060) * LogsNavigation: show older logs button when the order is descending * LogsNavigation: adjust styles for showing only older logs button * Logs Navigation: revert changes * Infinite scroll: add older logs button * Older logs button: show only in explore * chore: add unit test * Formatting * Update public/app/features/logs/components/InfiniteScroll.test.tsx Co-authored-by: Sven Grossmann * Chore: add missing translation * Chore: move the button a tiny bit --------- Co-authored-by: Sven Grossmann --- public/app/features/explore/Logs/Logs.tsx | 2 + .../features/explore/Logs/LogsNavigation.tsx | 1 + .../logs/components/InfiniteScroll.test.tsx | 35 +++++++++++- .../logs/components/InfiniteScroll.tsx | 55 ++++++++++++++++++- public/locales/en-US/grafana.json | 5 ++ public/locales/pseudo-LOCALE/grafana.json | 5 ++ 6 files changed, 99 insertions(+), 4 deletions(-) diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx index 5143293d345..ceeb7772aec 100644 --- a/public/app/features/explore/Logs/Logs.tsx +++ b/public/app/features/explore/Logs/Logs.tsx @@ -938,6 +938,7 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => { rows={logRows} scrollElement={logsContainerRef.current} sortOrder={logsSortOrder} + app={CoreApp.Explore} > { return { navContainer: css` max-height: ${navContainerHeight}; + ${oldestLogsFirst ? 'width: 58px;' : ''} display: flex; flex-direction: column; ${config.featureToggles.logsInfiniteScrolling diff --git a/public/app/features/logs/components/InfiniteScroll.test.tsx b/public/app/features/logs/components/InfiniteScroll.test.tsx index e90e93eb45b..e5e7854dd72 100644 --- a/public/app/features/logs/components/InfiniteScroll.test.tsx +++ b/public/app/features/logs/components/InfiniteScroll.test.tsx @@ -1,7 +1,8 @@ import { act, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { useEffect, useRef, useState } from 'react'; -import { LogRowModel, dateTimeForTimeZone } from '@grafana/data'; +import { CoreApp, LogRowModel, dateTimeForTimeZone } from '@grafana/data'; import { convertRawToRange } from '@grafana/data/src/datetime/rangeutil'; import { config } from '@grafana/runtime'; import { LogsSortOrder } from '@grafana/schema'; @@ -51,7 +52,13 @@ function ScrollWithWrapper({ children, ...props }: Props) { ); } -function setup(loadMoreMock: () => void, startPosition: number, rows: LogRowModel[], order: LogsSortOrder) { +function setup( + loadMoreMock: () => void, + startPosition: number, + rows: LogRowModel[], + order: LogsSortOrder, + app?: CoreApp +) { const { element, events } = getMockElement(startPosition); function scrollTo(position: number) { @@ -84,6 +91,7 @@ function setup(loadMoreMock: () => void, startPosition: number, rows: LogRowMode scrollElement={element as unknown as HTMLDivElement} loadMoreLogs={loadMoreMock} topScrollEnabled + app={app} >
@@ -267,6 +275,28 @@ describe('InfiniteScroll', () => { }); } ); + + describe('In Explore', () => { + test('Requests older logs from the oldest timestamp', async () => { + const loadMoreMock = jest.fn(); + const rows = createLogRows( + absoluteRange.from + 2 * SCROLLING_THRESHOLD, + absoluteRange.to - 2 * SCROLLING_THRESHOLD + ); + setup(loadMoreMock, 0, rows, LogsSortOrder.Ascending, CoreApp.Explore); + + expect(await screen.findByTestId('contents')).toBeInTheDocument(); + + await screen.findByText('Older logs'); + + await userEvent.click(screen.getByText('Older logs')); + + expect(loadMoreMock).toHaveBeenCalledWith({ + from: absoluteRange.from, + to: rows[0].timeEpochMs, + }); + }); + }); }); function createLogRows(from: number, to: number) { @@ -292,6 +322,7 @@ function getMockElement(scrollTop: number) { clientHeight: 40, scrollTop, scrollTo: jest.fn(), + scroll: jest.fn(), }; return { element, events }; diff --git a/public/app/features/logs/components/InfiniteScroll.tsx b/public/app/features/logs/components/InfiniteScroll.tsx index 65037b4a8b5..9572f0ca283 100644 --- a/public/app/features/logs/components/InfiniteScroll.tsx +++ b/public/app/features/logs/components/InfiniteScroll.tsx @@ -1,14 +1,17 @@ import { css } from '@emotion/css'; -import { ReactNode, useEffect, useRef, useState } from 'react'; +import { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; -import { AbsoluteTimeRange, LogRowModel, TimeRange } from '@grafana/data'; +import { AbsoluteTimeRange, CoreApp, LogRowModel, TimeRange } from '@grafana/data'; import { convertRawToRange, isRelativeTime, isRelativeTimeRange } from '@grafana/data/src/datetime/rangeutil'; import { config, reportInteraction } from '@grafana/runtime'; import { LogsSortOrder, TimeZone } from '@grafana/schema'; +import { Button, Icon } from '@grafana/ui'; +import { Trans } from 'app/core/internationalization'; import { LoadingIndicator } from './LoadingIndicator'; export type Props = { + app?: CoreApp; children: ReactNode; loading: boolean; loadMoreLogs?: (range: AbsoluteTimeRange) => void; @@ -21,6 +24,7 @@ export type Props = { }; export const InfiniteScroll = ({ + app, children, loading, loadMoreLogs, @@ -135,10 +139,37 @@ export const InfiniteScroll = ({ const hideTopMessage = sortOrder === LogsSortOrder.Descending && isRelativeTime(range.raw.to); const hideBottomMessage = sortOrder === LogsSortOrder.Ascending && isRelativeTime(range.raw.to); + const loadOlderLogs = useCallback(() => { + //If we are not on the last page, use next page's range + reportInteraction('grafana_explore_logs_infinite_pagination_clicked', { + pageType: 'olderLogsButton', + }); + const newRange = canScrollTop(getVisibleRange(rows), range, timeZone, sortOrder); + if (!newRange) { + setUpperOutOfRange(true); + return; + } + setUpperOutOfRange(false); + loadMoreLogs?.(newRange); + setUpperLoading(true); + scrollElement?.scroll({ + behavior: 'auto', + top: 0, + }); + }, [loadMoreLogs, range, rows, scrollElement, sortOrder, timeZone]); + return ( <> {upperLoading && } {!hideTopMessage && upperOutOfRange && outOfRangeMessage} + {sortOrder === LogsSortOrder.Ascending && app === CoreApp.Explore && ( + + )} {children} {!hideBottomMessage && lowerOutOfRange && outOfRangeMessage} {lowerLoading && } @@ -151,6 +182,26 @@ const styles = { textAlign: 'center', padding: 0.25, }), + navButton: css({ + width: '58px', + height: '68px', + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + lineHeight: '1', + position: 'absolute', + top: 0, + right: -3, + zIndex: 1, + }), + navButtonContent: css({ + display: 'flex', + flexDirection: 'column', + justifyContent: 'center', + alignItems: 'center', + whiteSpace: 'normal', + }), }; const outOfRangeMessage = ( diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index e813d8380fe..8c7211f6943 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1056,6 +1056,11 @@ "new-to-question": "New to Grafana?" } }, + "logs": { + "infinite-scroll": { + "older-logs": "Older logs" + } + }, "migrate-to-cloud": { "build-snapshot": { "description": "This tool can migrate some resources from this installation to your cloud stack. To get started, you'll need to create a snapshot of this installation. Creating a snapshot typically takes less than two minutes. The snapshot is stored alongside this Grafana installation.", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index f8591176bf9..7420889142b 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -1056,6 +1056,11 @@ "new-to-question": "Ńęŵ ŧő Ğřäƒäʼnä?" } }, + "logs": { + "infinite-scroll": { + "older-logs": "Øľđęř ľőģş" + } + }, "migrate-to-cloud": { "build-snapshot": { "description": "Ŧĥįş ŧőőľ čäʼn mįģřäŧę şőmę řęşőūřčęş ƒřőm ŧĥįş įʼnşŧäľľäŧįőʼn ŧő yőūř čľőūđ şŧäčĸ. Ŧő ģęŧ şŧäřŧęđ, yőū'ľľ ʼnęęđ ŧő čřęäŧę ä şʼnäpşĥőŧ őƒ ŧĥįş įʼnşŧäľľäŧįőʼn. Cřęäŧįʼnģ ä şʼnäpşĥőŧ ŧypįčäľľy ŧäĸęş ľęşş ŧĥäʼn ŧŵő mįʼnūŧęş. Ŧĥę şʼnäpşĥőŧ įş şŧőřęđ äľőʼnģşįđę ŧĥįş Ğřäƒäʼnä įʼnşŧäľľäŧįőʼn.", From 530355d5dbb0263cbe3e59ecd72041629276c81a Mon Sep 17 00:00:00 2001 From: Abdessamad Enabih <70137147+AbdessamadEnabih@users.noreply.github.com> Date: Thu, 15 Aug 2024 16:24:38 +0100 Subject: [PATCH 102/229] Profile: Add tooltip to revoke session button (#91858) * A11y: Buttons in profile/sessions do not provide tooltips * Adressing PR comment for #91858 --- public/app/features/profile/UserSessions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/profile/UserSessions.tsx b/public/app/features/profile/UserSessions.tsx index 07e081df46e..6ffb106a0ec 100644 --- a/public/app/features/profile/UserSessions.tsx +++ b/public/app/features/profile/UserSessions.tsx @@ -60,6 +60,7 @@ class UserSessions extends PureComponent {

diff --git a/public/app/features/admin/UserListPublicDashboardPage/DeleteUserModalButton.tsx b/public/app/features/admin/UserListPublicDashboardPage/DeleteUserModalButton.tsx index 98d427d5569..fd978a9ba4a 100644 --- a/public/app/features/admin/UserListPublicDashboardPage/DeleteUserModalButton.tsx +++ b/public/app/features/admin/UserListPublicDashboardPage/DeleteUserModalButton.tsx @@ -1,6 +1,7 @@ import { css } from '@emotion/css'; import { GrafanaTheme2 } from '@grafana/data/src'; +import { config } from '@grafana/runtime'; import { Button, Modal, ModalsController, useStyles2 } from '@grafana/ui/src'; import { Trans, t } from 'app/core/internationalization'; import { SessionUser } from 'app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboardUtils'; @@ -29,12 +30,21 @@ const DeleteUserModal = ({ user, hideModal }: { user: SessionUser; hideModal: ()

- - This action will immediately revoke {{ email: user.email }}'s access to all public dashboards. - + {config.featureToggles.newDashboardSharingComponent ? ( + + This action will immediately revoke {{ email: user.email }}'s access to all shared dashboards. + + ) : ( + + This action will immediately revoke {{ email: user.email }}'s access to all public dashboards. + + )}

- - + {config.featureToggles.newDashboardSharingComponent ? ( + + + + + ) : ( + + + + + )} ); }; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 7858779fd21..a0753371a30 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -2156,14 +2156,20 @@ }, "share-panel": { "drawer": { + "new-library-panel-title": "New library panel", "share-embed-title": "Share embed", "share-link-title": "Link settings", "share-snapshot-title": "Share snapshot" }, "menu": { + "new-library-panel-title": "New library panel", "share-embed-title": "Share embed", "share-link-title": "Share link", "share-snapshot-title": "Share snapshot" + }, + "new-library-panel": { + "cancel-button": "Cancel", + "create-button": "Create library panel" } }, "share-playlist": { diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index deafbde58f3..ac9ac502962 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -2156,14 +2156,20 @@ }, "share-panel": { "drawer": { + "new-library-panel-title": "Ńęŵ ľįþřäřy päʼnęľ", "share-embed-title": "Ŝĥäřę ęmþęđ", "share-link-title": "Ŀįʼnĸ şęŧŧįʼnģş", "share-snapshot-title": "Ŝĥäřę şʼnäpşĥőŧ" }, "menu": { + "new-library-panel-title": "Ńęŵ ľįþřäřy päʼnęľ", "share-embed-title": "Ŝĥäřę ęmþęđ", "share-link-title": "Ŝĥäřę ľįʼnĸ", "share-snapshot-title": "Ŝĥäřę şʼnäpşĥőŧ" + }, + "new-library-panel": { + "cancel-button": "Cäʼnčęľ", + "create-button": "Cřęäŧę ľįþřäřy päʼnęľ" } }, "share-playlist": { From 17fd8a6bc3b9a3cf7bfc4122066abea7d871926a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:27:15 +0000 Subject: [PATCH 166/229] Update dependency @msagl/parser to v1.1.22 --- yarn.lock | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/yarn.lock b/yarn.lock index 2c7b2f9c05d..5e11117a297 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4861,7 +4861,7 @@ __metadata: languageName: node linkType: hard -"@msagl/core@npm:^1.1.19": +"@msagl/core@npm:^1.1.19, @msagl/core@npm:^1.1.22": version: 1.1.22 resolution: "@msagl/core@npm:1.1.22" dependencies: @@ -4874,25 +4874,25 @@ __metadata: languageName: node linkType: hard -"@msagl/drawing@npm:^1.1.19": - version: 1.1.19 - resolution: "@msagl/drawing@npm:1.1.19" +"@msagl/drawing@npm:^1.1.22": + version: 1.1.22 + resolution: "@msagl/drawing@npm:1.1.22" dependencies: - "@msagl/core": "npm:^1.1.19" - checksum: 10/b8963ab6f8dd7943a10d950abe11030996e12cd74503c6d704678dce54c1fd0f5ee1570cc893027e24150e475294d09a4ae4ac3be8f9e9f8d7382d7b7c5303a9 + "@msagl/core": "npm:^1.1.22" + checksum: 10/8027476475b6da6494f5034fe483c6508e2ec638abac504e879a2e0276bc86b22b5f7f11bcb20338a3339da609832ad577b896906a79397825ae1fc5823754f8 languageName: node linkType: hard "@msagl/parser@npm:^1.1.19": - version: 1.1.19 - resolution: "@msagl/parser@npm:1.1.19" + version: 1.1.22 + resolution: "@msagl/parser@npm:1.1.22" dependencies: - "@msagl/core": "npm:^1.1.19" - "@msagl/drawing": "npm:^1.1.19" + "@msagl/core": "npm:^1.1.22" + "@msagl/drawing": "npm:^1.1.22" "@types/parse-color": "npm:^1.0.1" dotparser: "npm:^1.1.1" parse-color: "npm:^1.0.0" - checksum: 10/349dcd57a3365628699b45172359363b86a1b27f8300b5b7fde97ba65eb512191a7433b72908a544fbbdc1d91bcaa9ca498faef345078367529820a47af38609 + checksum: 10/87e13379d99be4327c3101708ae0c3fb2c52c07cdc19db19e2abc3d20453522298af2f7c2e6ae366883227e191cf07913f91eaaf473012d0fe858b4acd2c735b languageName: node linkType: hard From 5bfce7f5908ccd8736338b85e7379952eb1c5a86 Mon Sep 17 00:00:00 2001 From: Sergej-Vlasov <37613182+Sergej-Vlasov@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:19:28 +0300 Subject: [PATCH 167/229] DashboardGridItem - increase timeout in flaky panel repeat test (#92133) increase timeout in flaky panel repeat --- .../features/dashboard-scene/scene/DashboardGridItem.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardGridItem.test.tsx b/public/app/features/dashboard-scene/scene/DashboardGridItem.test.tsx index a0e90f58d7a..e56fe084dcb 100644 --- a/public/app/features/dashboard-scene/scene/DashboardGridItem.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardGridItem.test.tsx @@ -47,7 +47,7 @@ describe('PanelRepeaterGridItem', () => { expect(repeater.state.repeatedPanels?.length).toBe(0); - await new Promise((r) => setTimeout(r, 10)); + await new Promise((r) => setTimeout(r, 100)); expect(repeater.state.repeatedPanels?.length).toBe(1); }); From 0631322d36b2b12419599865abb2216a89c80aef Mon Sep 17 00:00:00 2001 From: brendamuir <100768211+brendamuir@users.noreply.github.com> Date: Tue, 20 Aug 2024 16:22:07 +0200 Subject: [PATCH 168/229] Alerting docs: adds sns integration (#92075) * Alerting docs: adds sns integration * deletes 2000 alert rule limit * added .md ext and set weight field to 0 --------- Co-authored-by: tonypowa --- .../create-grafana-managed-rule.md | 7 +-- .../integrations/configure-amazon-sns.md | 57 +++++++++++++++++++ .../integrations/configure-discord.md | 2 +- .../integrations/configure-email.md | 2 +- .../integrations/configure-google-chat.md | 2 +- .../integrations/configure-oncall.md | 2 +- .../integrations/configure-opsgenie.md | 2 +- .../integrations/configure-slack.md | 2 +- .../integrations/configure-teams.md | 2 +- .../integrations/configure-telegram.md | 2 +- .../integrations/pager-duty.md | 2 +- .../integrations/webhook-notifier.md | 2 +- 12 files changed, 69 insertions(+), 15 deletions(-) create mode 100644 docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md diff --git a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md index 65bd708d356..e0407e2062c 100644 --- a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md +++ b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md @@ -83,11 +83,8 @@ Grafana-managed rules are the most flexible alert rule type. They allow you to c Multiple alert instances can be created as a result of one alert rule (also known as a multi-dimensional alerting). {{% admonition type="note" %}} -For Grafana Cloud, there are limits on how many Grafana-managed alert rules you can create. These are as follows: - -- Free: 100 alert rules -- Paid: 2000 alert rules - {{% /admonition %}} +For Grafana Cloud, you can create 100 free Grafana-managed alert rules. +{{% /admonition %}} Grafana managed alert rules can only be edited or deleted by users with Edit permissions for the folder storing the rules. diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md new file mode 100644 index 00000000000..6cd38615744 --- /dev/null +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns.md @@ -0,0 +1,57 @@ +--- +canonical: https://grafana.com/docs/grafana/latest/alerting/configure-notifications/manage-contact-points/integrations/configure-amazon-sns/ +description: Configure the Grafana Alerting - Amazon SNS integration to receive alert notifications when your alerts are firing. +keywords: + - grafana + - alerting + - Amazon SNS + - integration +labels: + products: + - cloud + - enterprise + - oss +menuTitle: Amazon SNS +title: Configure Amazon SNS for Alerting +weight: 0 +--- + +# Configure Amazon SNS for Alerting + +Use the Grafana Alerting - Amazon SNS integration to send notifications to Amazon SNS when your alerts are firing. + +## Before you begin + +To configure Amazon SNS to receive alert notifications, complete the following steps. + +1. Create a new topic in https://console.aws.amazon.com/sns. +1. Open the topic and create a new subscription. +1. Choose the protocol HTTPS. +1. Copy the URL. + +For more information, refer to [Amazon SNS documentation](https://docs.aws.amazon.com/sns/latest/dg/welcome.html). + +## Procedure + +To create your Amazon SNS integration in Grafana Alerting, complete the following steps. + +1. Navigate to **Alerts & IRM** -> **Alerting** -> **Contact points**. +1. Click **+ Add contact point**. +1. Enter a contact point name. +1. From the Integration list, select **AWS SNS**. +1. Copy in the URL from above into the **The Amazon SNS API URL** field. +1. Click **Test** to check that your integration works. +1. Click **Save contact point**. + +## Next steps + +The Amazon SNS contact point is ready to receive alert notifications. + +To add this contact point to your alert, complete the following steps. + +1. In Grafana, navigate to **Alerting** > **Alert rules**. +1. Edit or create a new alert rule. +1. Scroll down to the **Configure labels and notifications** section. +1. Under Notifications click **Select contact point**. +1. From the drop-down menu, select the previously created contact point. +1. **Click Save rule and exit**. diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-discord.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-discord.md index cb88c6bb688..7c5baff4446 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-discord.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-discord.md @@ -13,7 +13,7 @@ labels: - oss menuTitle: Discord title: Configure Discord for Alerting -weight: 10 +weight: 0 --- # Configure Discord for Alerting diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-email.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-email.md index a85be6fe3bf..8295288797b 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-email.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-email.md @@ -13,7 +13,7 @@ labels: - oss menuTitle: Email title: Configure email for Alerting -weight: 20 +weight: 0 --- # Configure email for Alerting diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-google-chat.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-google-chat.md index 531167a370d..e20a31606e9 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-google-chat.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-google-chat.md @@ -13,7 +13,7 @@ labels: - oss menuTitle: Google Chat title: Configure Google Chat for Alerting -weight: 30 +weight: 0 --- # Configure Google Chat for Alerting diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-oncall.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-oncall.md index 5aab071e1ad..3cfed955a00 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-oncall.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-oncall.md @@ -18,7 +18,7 @@ labels: - oss menuTitle: Grafana OnCall title: Configure Grafana OnCall for Alerting -weight: 40 +weight: 0 --- # Configure Grafana OnCall for Alerting diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-opsgenie.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-opsgenie.md index 47cdf1007be..0ffa3dcf20e 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-opsgenie.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-opsgenie.md @@ -13,7 +13,7 @@ labels: - oss menuTitle: Opsgenie title: Configure Opsgenie for Alerting -weight: 60 +weight: 0 --- # Configure Opsgenie for Alerting diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-slack.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-slack.md index efd20e7414a..6777771bf9a 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-slack.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-slack.md @@ -13,7 +13,7 @@ labels: - oss menuTitle: Slack title: Configure Slack for Alerting -weight: 80 +weight: 0 refs: nested-policy: - pattern: /docs/grafana/ diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-teams.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-teams.md index f57494abe04..1141af8db6c 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-teams.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-teams.md @@ -13,7 +13,7 @@ labels: - oss menuTitle: Microsoft Teams title: Configure Microsoft Teams for Alerting -weight: 50 +weight: 0 --- # Configure Microsoft Teams for Alerting diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-telegram.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-telegram.md index 318ea127d96..dd5ca20d7bb 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-telegram.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/configure-telegram.md @@ -13,7 +13,7 @@ labels: - oss menuTitle: Telegram title: Configure Telegram for Alerting -weight: 90 +weight: 0 --- # Configure Telegram for Alerting diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/pager-duty.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/pager-duty.md index 1f7e9b1247b..d6202d7adc4 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/pager-duty.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/pager-duty.md @@ -14,7 +14,7 @@ labels: - oss menuTitle: PagerDuty title: Configure PagerDuty for Alerting -weight: 70 +weight: 0 --- # Configure PagerDuty for Alerting diff --git a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md index 1e1d771caae..752f3b9fa52 100644 --- a/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md +++ b/docs/sources/alerting/configure-notifications/manage-contact-points/integrations/webhook-notifier.md @@ -21,7 +21,7 @@ labels: - oss menuTitle: Webhook notifier title: Configure the webhook notifier for Alerting -weight: 100 +weight: 0 --- # Configure the webhook notifier for Alerting From 6f63def2835366ca11324fcdade2fd0089ed7d15 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Tue, 20 Aug 2024 17:02:35 +0200 Subject: [PATCH 169/229] Alerting: Fix long Alertmanager names overflowing the window (#92023) fix long names overflowing the window --- .../unified/components/AlertManagerPicker.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/public/app/features/alerting/unified/components/AlertManagerPicker.tsx b/public/app/features/alerting/unified/components/AlertManagerPicker.tsx index 75bc97f6512..9087ef4f269 100644 --- a/public/app/features/alerting/unified/components/AlertManagerPicker.tsx +++ b/public/app/features/alerting/unified/components/AlertManagerPicker.tsx @@ -1,8 +1,8 @@ import { css } from '@emotion/css'; -import { useMemo } from 'react'; +import { useMemo, ComponentProps } from 'react'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { InlineField, Select, useStyles2 } from '@grafana/ui'; +import { InlineField, Select, SelectMenuOptions, useStyles2 } from '@grafana/ui'; import { useAlertmanager } from '../state/AlertmanagerContext'; import { AlertManagerDataSource, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; @@ -12,15 +12,15 @@ interface Props { } function getAlertManagerLabel(alertManager: AlertManagerDataSource) { - return alertManager.name === GRAFANA_RULES_SOURCE_NAME ? 'Grafana' : alertManager.name.slice(0, 37); + return alertManager.name === GRAFANA_RULES_SOURCE_NAME ? 'Grafana' : alertManager.name; } export const AlertManagerPicker = ({ disabled = false }: Props) => { const styles = useStyles2(getStyles); const { selectedAlertmanager, availableAlertManagers, setSelectedAlertmanager } = useAlertmanager(); - const options: Array> = useMemo(() => { - return availableAlertManagers.map((ds) => ({ + const options = useMemo(() => { + return availableAlertManagers.map>((ds) => ({ label: getAlertManagerLabel(ds), value: ds.name, imgUrl: ds.imgUrl, @@ -44,10 +44,10 @@ export const AlertManagerPicker = ({ disabled = false }: Props) => { } }} options={options} - maxMenuHeight={500} noOptionsMessage="No datasources found" value={selectedAlertmanager} getOptionLabel={(o) => o.label} + components={{ Option: CustomOption }} /> ); @@ -58,3 +58,11 @@ const getStyles = (theme: GrafanaTheme2) => ({ margin: 0, }), }); + +// custom option that overwrites the default "white-space: nowrap" for Alertmanager names that are really long +const CustomOption = (props: ComponentProps) => ( +
{label}
} + /> +); From cd4b7ef9dbf4551966546c47d11173b54a94a3ae Mon Sep 17 00:00:00 2001 From: Alexa V <239999+axelavargas@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:05:12 +0200 Subject: [PATCH 170/229] Dashboard Scene: Fix snapshots not displaying variables values (#88967) * Use new snapshot variables from scenes * Add snapshotVariable implementation * Refactor: Extract variables logic from transforSaveModelToScene file --------- Co-authored-by: Dominik Prokop --- kinds/dashboard/dashboard_kind.cue | 5 +- packages/grafana-data/src/index.ts | 1 + .../grafana-data/src/types/templateVars.ts | 8 +- .../raw/dashboard/x/dashboard_types.gen.ts | 2 +- pkg/kinds/dashboard/dashboard_spec_gen.go | 1 + .../SnapshotVariable.test.tsx | 33 + .../custom-variables/SnapshotVariable.tsx | 81 ++ .../transformSaveModelToScene.test.ts | 766 +++-------------- .../transformSaveModelToScene.ts | 162 +--- .../settings/variables/utils.ts | 3 +- .../dashboard-scene/utils/variables.test.ts | 775 ++++++++++++++++++ .../dashboard-scene/utils/variables.ts | 238 ++++++ public/app/features/variables/guard.test.ts | 26 +- .../variables/state/__tests__/fixtures.ts | 11 + public/app/plugins/panel/logs/panelcfg.cue | 4 +- 15 files changed, 1286 insertions(+), 830 deletions(-) create mode 100644 public/app/features/dashboard-scene/serialization/custom-variables/SnapshotVariable.test.tsx create mode 100644 public/app/features/dashboard-scene/serialization/custom-variables/SnapshotVariable.tsx create mode 100644 public/app/features/dashboard-scene/utils/variables.test.ts create mode 100644 public/app/features/dashboard-scene/utils/variables.ts diff --git a/kinds/dashboard/dashboard_kind.cue b/kinds/dashboard/dashboard_kind.cue index 58ae9457718..6a24fc92266 100644 --- a/kinds/dashboard/dashboard_kind.cue +++ b/kinds/dashboard/dashboard_kind.cue @@ -294,7 +294,8 @@ lineage: schemas: [{ // `textbox`: Display a free text input field with an optional default value. // `custom`: Define the variable options manually using a comma-separated list. // `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables - #VariableType: "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" | "system" @cuetsy(kind="type") @grafanamaturity(NeedsExpertReview) + #VariableType: "query" | "adhoc" | "groupby" | "constant" | "datasource" | "interval" | "textbox" | "custom" | + "system" | "snapshot" @cuetsy(kind="type") @grafanamaturity(NeedsExpertReview) // Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value. // Continuous color interpolates a color using the percentage of a value relative to min and max. @@ -594,7 +595,7 @@ lineage: schemas: [{ // Dynamically load the panel libraryPanel?: #LibraryPanelRef - // Sets panel queries cache timeout. + // Sets panel queries cache timeout. cacheTimeout?: string // Overrides the data source configured time-to-live for a query cache item in milliseconds diff --git a/packages/grafana-data/src/index.ts b/packages/grafana-data/src/index.ts index 005e89554da..e2cd45f4fb2 100644 --- a/packages/grafana-data/src/index.ts +++ b/packages/grafana-data/src/index.ts @@ -515,6 +515,7 @@ export { type UserVariableModel, type SystemVariable, type BaseVariableModel, + type SnapshotVariableModel, } from './types/templateVars'; export { type Threshold, ThresholdsMode, type ThresholdsConfig } from './types/thresholds'; export { diff --git a/packages/grafana-data/src/types/templateVars.ts b/packages/grafana-data/src/types/templateVars.ts index 1b01e5b9e53..14a1cd38fc8 100644 --- a/packages/grafana-data/src/types/templateVars.ts +++ b/packages/grafana-data/src/types/templateVars.ts @@ -22,7 +22,8 @@ export type TypedVariableModel = | CustomVariableModel | UserVariableModel | OrgVariableModel - | DashboardVariableModel; + | DashboardVariableModel + | SnapshotVariableModel; export enum VariableRefresh { never, // removed from the UI @@ -178,3 +179,8 @@ export interface BaseVariableModel { description: string | null; usedInRepeat?: boolean; } + +export interface SnapshotVariableModel extends VariableWithOptions { + type: 'snapshot'; + query: string; +} diff --git a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts index 784b96494f4..a67ab34ae38 100644 --- a/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts +++ b/packages/grafana-schema/src/raw/dashboard/x/dashboard_types.gen.ts @@ -349,7 +349,7 @@ export type DashboardLinkType = ('link' | 'dashboards'); * `custom`: Define the variable options manually using a comma-separated list. * `system`: Variables defined by Grafana. See: https://grafana.com/docs/grafana/latest/dashboards/variables/add-template-variables/#global-variables */ -export type VariableType = ('query' | 'adhoc' | 'groupby' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom' | 'system'); +export type VariableType = ('query' | 'adhoc' | 'groupby' | 'constant' | 'datasource' | 'interval' | 'textbox' | 'custom' | 'system' | 'snapshot'); /** * Color mode for a field. You can specify a single color, or select a continuous (gradient) color schemes, based on a value. diff --git a/pkg/kinds/dashboard/dashboard_spec_gen.go b/pkg/kinds/dashboard/dashboard_spec_gen.go index 60ac16f3ea0..de16129aa20 100644 --- a/pkg/kinds/dashboard/dashboard_spec_gen.go +++ b/pkg/kinds/dashboard/dashboard_spec_gen.go @@ -168,6 +168,7 @@ const ( VariableTypeGroupby VariableType = "groupby" VariableTypeInterval VariableType = "interval" VariableTypeQuery VariableType = "query" + VariableTypeSnapshot VariableType = "snapshot" VariableTypeSystem VariableType = "system" VariableTypeTextbox VariableType = "textbox" ) diff --git a/public/app/features/dashboard-scene/serialization/custom-variables/SnapshotVariable.test.tsx b/public/app/features/dashboard-scene/serialization/custom-variables/SnapshotVariable.test.tsx new file mode 100644 index 00000000000..39beea7a92a --- /dev/null +++ b/public/app/features/dashboard-scene/serialization/custom-variables/SnapshotVariable.test.tsx @@ -0,0 +1,33 @@ +import { SnapshotVariable } from './SnapshotVariable'; + +describe('SnapshotVariable', () => { + describe('SnapshotVariable state', () => { + it('should create a new snapshotVariable when custom variable is passed', () => { + const { multiVariable } = setupScene(); + const snapshot = new SnapshotVariable(multiVariable); + //expect snapshot to be defined + expect(snapshot).toBeDefined(); + expect(snapshot.state).toBeDefined(); + expect(snapshot.state.type).toBe('snapshot'); + expect(snapshot.state.isReadOnly).toBe(true); + expect(snapshot.state.value).toBe(multiVariable.value); + expect(snapshot.state.text).toBe(multiVariable.text); + expect(snapshot.state.hide).toBe(multiVariable.hide); + }); + }); +}); + +function setupScene() { + // create custom variable type custom + + const multiVariable = { + name: 'Multi', + description: 'Define variable values manually', + text: 'myMultiText', + value: 'myMultiValue', + multi: true, + hide: 0, + }; + + return { multiVariable }; +} diff --git a/public/app/features/dashboard-scene/serialization/custom-variables/SnapshotVariable.tsx b/public/app/features/dashboard-scene/serialization/custom-variables/SnapshotVariable.tsx new file mode 100644 index 00000000000..3a84ade2759 --- /dev/null +++ b/public/app/features/dashboard-scene/serialization/custom-variables/SnapshotVariable.tsx @@ -0,0 +1,81 @@ +import { Observable, map, of } from 'rxjs'; + +import { + MultiValueVariable, + MultiValueVariableState, + SceneComponentProps, + ValidateAndUpdateResult, + VariableDependencyConfig, + VariableValueOption, + renderSelectForVariable, + sceneGraph, + VariableGetOptionsArgs, +} from '@grafana/scenes'; + +export interface SnapshotVariableState extends MultiValueVariableState { + query?: string; +} + +export class SnapshotVariable extends MultiValueVariable { + protected _variableDependency = new VariableDependencyConfig(this, { + statePaths: [], + }); + + public constructor(initialState: Partial) { + super({ + name: '', + type: 'snapshot', + isReadOnly: true, + query: '', + value: '', + text: '', + options: [], + ...initialState, + }); + } + + public getValueOptions(args: VariableGetOptionsArgs): Observable { + const interpolated = sceneGraph.interpolate(this, this.state.query); + const match = interpolated.match(/(?:\\,|[^,])+/g) ?? []; + + const options = match.map((text) => { + text = text.replace(/\\,/g, ','); + const textMatch = /^(.+)\s:\s(.+)$/g.exec(text) ?? []; + if (textMatch.length === 3) { + const [, key, value] = textMatch; + return { label: key.trim(), value: value.trim() }; + } else { + return { label: text.trim(), value: text.trim() }; + } + }); + + return of(options); + } + + public validateAndUpdate(): Observable { + return this.getValueOptions({}).pipe( + map((options) => { + if (this.state.options !== options) { + this._updateValueGivenNewOptions(options); + } + return {}; + }) + ); + } + + public static Component = ({ model }: SceneComponentProps>) => { + return renderSelectForVariable(model); + }; + // we will always preserve the current value and text for snapshots + private _updateValueGivenNewOptions(options: VariableValueOption[]) { + const { value: currentValue, text: currentText } = this.state; + const stateUpdate: Partial = { + options, + loading: false, + value: currentValue ?? [], + text: currentText ?? [], + }; + + this.setState(stateUpdate); + } +} diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts index ea3e0c936f5..c779254f1d3 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.test.ts @@ -1,24 +1,10 @@ -import { - LoadingState, - ConstantVariableModel, - CustomVariableModel, - DataSourceVariableModel, - QueryVariableModel, - IntervalVariableModel, - TypedVariableModel, - TextBoxVariableModel, - GroupByVariableModel, -} from '@grafana/data'; +import { LoadingState } from '@grafana/data'; import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; import { config } from '@grafana/runtime'; import { AdHocFiltersVariable, behaviors, ConstantVariable, - CustomVariable, - DataSourceVariable, - GroupByVariable, - QueryVariable, SceneDataLayerControls, SceneDataTransformer, SceneGridLayout, @@ -50,12 +36,12 @@ import { getQueryRunnerFor } from '../utils/utils'; import { buildNewDashboardSaveModel } from './buildNewDashboardSaveModel'; import { GRAFANA_DATASOURCE_REF } from './const'; +import { SnapshotVariable } from './custom-variables/SnapshotVariable'; import dashboard_to_load1 from './testfiles/dashboard_to_load1.json'; import repeatingRowsAndPanelsDashboardJson from './testfiles/repeating_rows_and_panels.json'; import { createDashboardSceneFromDashboardModel, buildGridItemForPanel, - createSceneVariableFromVariableModel, transformSaveModelToScene, convertOldSnapshotToScenesSnapshot, buildGridItemForLibPanel, @@ -193,6 +179,113 @@ describe('transformSaveModelToScene', () => { }); }); + describe('When creating a snapshot dashboard scene', () => { + it('should initialize a dashboard scene with SnapshotVariables', () => { + const customVariable = { + current: { + selected: false, + text: 'a', + value: 'a', + }, + hide: 0, + includeAll: false, + multi: false, + name: 'custom0', + options: [], + query: 'a,b,c,d', + skipUrlSync: false, + type: 'custom' as VariableType, + rootStateKey: 'N4XLmH5Vz', + }; + + const intervalVariable = { + current: { + selected: false, + text: '10s', + value: '10s', + }, + hide: 0, + includeAll: false, + multi: false, + name: 'interval0', + options: [], + query: '10s,20s,30s', + skipUrlSync: false, + type: 'interval' as VariableType, + rootStateKey: 'N4XLmH5Vz', + }; + + const adHocVariable = { + global: false, + name: 'CoolFilters', + label: 'CoolFilters Label', + type: 'adhoc' as VariableType, + datasource: { + uid: 'gdev-prometheus', + type: 'prometheus', + }, + filters: [ + { + key: 'filterTest', + operator: '=', + value: 'test', + }, + ], + baseFilters: [ + { + key: 'baseFilterTest', + operator: '=', + value: 'test', + }, + ], + hide: 0, + index: 0, + }; + + const snapshot = { + ...defaultDashboard, + title: 'snapshot dash', + uid: 'test-uid', + time: { from: 'now-10h', to: 'now' }, + weekStart: 'saturday', + fiscalYearStartMonth: 2, + timezone: 'America/New_York', + timepicker: { + ...defaultTimePickerConfig, + hidden: true, + }, + links: [{ ...NEW_LINK, title: 'Link 1' }], + templating: { + list: [customVariable, adHocVariable, intervalVariable], + }, + }; + + const oldModel = new DashboardModel(snapshot, { isSnapshot: true }); + const scene = createDashboardSceneFromDashboardModel(oldModel, snapshot); + + // check variables were converted to snapshot variables + expect(scene.state.$variables?.state.variables).toHaveLength(3); + expect(scene.state.$variables?.getByName('custom0')).toBeInstanceOf(SnapshotVariable); + expect(scene.state.$variables?.getByName('CoolFilters')).toBeInstanceOf(AdHocFiltersVariable); + expect(scene.state.$variables?.getByName('interval0')).toBeInstanceOf(SnapshotVariable); + // custom snapshot + const customSnapshot = scene.state.$variables?.getByName('custom0') as SnapshotVariable; + expect(customSnapshot.state.value).toBe('a'); + expect(customSnapshot.state.text).toBe('a'); + expect(customSnapshot.state.isReadOnly).toBe(true); + // adhoc snapshot + const adhocSnapshot = scene.state.$variables?.getByName('CoolFilters') as AdHocFiltersVariable; + expect(adhocSnapshot.state.filters).toEqual(adHocVariable.filters); + expect(adhocSnapshot.state.readOnly).toBe(true); + + // interval snapshot + const intervalSnapshot = scene.state.$variables?.getByName('interval0') as SnapshotVariable; + expect(intervalSnapshot.state.value).toBe('10s'); + expect(intervalSnapshot.state.text).toBe('10s'); + expect(intervalSnapshot.state.isReadOnly).toBe(true); + }); + }); + describe('when organizing panels as scene children', () => { it('should create panels within collapsed rows', () => { const panel = createPanelSaveModel({ @@ -593,647 +686,6 @@ describe('transformSaveModelToScene', () => { }); }); - describe('when creating variables objects', () => { - it('should migrate custom variable', () => { - const variable: CustomVariableModel = { - current: { - selected: false, - text: 'a', - value: 'a', - }, - hide: 0, - includeAll: false, - multi: false, - name: 'query0', - options: [ - { - selected: true, - text: 'a', - value: 'a', - }, - { - selected: false, - text: 'b', - value: 'b', - }, - { - selected: false, - text: 'c', - value: 'c', - }, - { - selected: false, - text: 'd', - value: 'd', - }, - ], - query: 'a,b,c,d', - skipUrlSync: false, - type: 'custom', - rootStateKey: 'N4XLmH5Vz', - id: 'query0', - global: false, - index: 0, - state: LoadingState.Done, - error: null, - description: null, - allValue: null, - }; - - const migrated = createSceneVariableFromVariableModel(variable); - const { key, ...rest } = migrated.state; - - expect(migrated).toBeInstanceOf(CustomVariable); - expect(rest).toEqual({ - allValue: undefined, - defaultToAll: false, - description: null, - includeAll: false, - isMulti: false, - label: undefined, - name: 'query0', - options: [], - query: 'a,b,c,d', - skipUrlSync: false, - text: 'a', - type: 'custom', - value: 'a', - hide: 0, - }); - }); - - it('should migrate query variable with definition', () => { - const variable: QueryVariableModel = { - allValue: null, - current: { - text: 'America', - value: 'America', - selected: false, - }, - datasource: { - uid: 'P15396BDD62B2BE29', - type: 'influxdb', - }, - definition: 'SHOW TAG VALUES WITH KEY = "datacenter"', - hide: 0, - includeAll: false, - label: 'Datacenter', - multi: false, - name: 'datacenter', - options: [ - { - text: 'America', - value: 'America', - selected: true, - }, - { - text: 'Africa', - value: 'Africa', - selected: false, - }, - { - text: 'Asia', - value: 'Asia', - selected: false, - }, - { - text: 'Europe', - value: 'Europe', - selected: false, - }, - ], - query: 'SHOW TAG VALUES WITH KEY = "datacenter" ', - refresh: 1, - regex: '', - skipUrlSync: false, - sort: 0, - type: 'query', - rootStateKey: '000000002', - id: 'datacenter', - global: false, - index: 0, - state: LoadingState.Done, - error: null, - description: null, - }; - - const migrated = createSceneVariableFromVariableModel(variable); - const { key, ...rest } = migrated.state; - - expect(migrated).toBeInstanceOf(QueryVariable); - expect(rest).toEqual({ - allValue: undefined, - datasource: { - type: 'influxdb', - uid: 'P15396BDD62B2BE29', - }, - defaultToAll: false, - description: null, - includeAll: false, - isMulti: false, - label: 'Datacenter', - name: 'datacenter', - options: [], - query: 'SHOW TAG VALUES WITH KEY = "datacenter" ', - refresh: 1, - regex: '', - skipUrlSync: false, - sort: 0, - text: 'America', - type: 'query', - value: 'America', - hide: 0, - definition: 'SHOW TAG VALUES WITH KEY = "datacenter"', - }); - }); - - it('should migrate datasource variable', () => { - const variable: DataSourceVariableModel = { - id: 'query1', - rootStateKey: 'N4XLmH5Vz', - name: 'query1', - type: 'datasource', - global: false, - index: 1, - hide: 0, - skipUrlSync: false, - state: LoadingState.Done, - error: null, - description: null, - current: { - value: ['gdev-prometheus', 'gdev-slow-prometheus'], - text: ['gdev-prometheus', 'gdev-slow-prometheus'], - selected: true, - }, - regex: '/^gdev/', - options: [ - { - text: 'All', - value: '$__all', - selected: false, - }, - { - text: 'gdev-prometheus', - value: 'gdev-prometheus', - selected: true, - }, - { - text: 'gdev-slow-prometheus', - value: 'gdev-slow-prometheus', - selected: false, - }, - ], - query: 'prometheus', - multi: true, - includeAll: true, - refresh: 1, - allValue: 'Custom all', - }; - - const migrated = createSceneVariableFromVariableModel(variable); - const { key, ...rest } = migrated.state; - - expect(migrated).toBeInstanceOf(DataSourceVariable); - expect(rest).toEqual({ - allValue: 'Custom all', - defaultToAll: true, - includeAll: true, - label: undefined, - name: 'query1', - options: [], - pluginId: 'prometheus', - regex: '/^gdev/', - skipUrlSync: false, - text: ['gdev-prometheus', 'gdev-slow-prometheus'], - type: 'datasource', - value: ['gdev-prometheus', 'gdev-slow-prometheus'], - isMulti: true, - description: null, - hide: 0, - }); - }); - - it('should migrate constant variable', () => { - const variable: ConstantVariableModel = { - hide: 2, - label: 'constant', - name: 'constant', - skipUrlSync: false, - type: 'constant', - rootStateKey: 'N4XLmH5Vz', - current: { - selected: true, - text: 'test', - value: 'test', - }, - options: [ - { - selected: true, - text: 'test', - value: 'test', - }, - ], - query: 'test', - id: 'constant', - global: false, - index: 3, - state: LoadingState.Done, - error: null, - description: null, - }; - - const migrated = createSceneVariableFromVariableModel(variable); - const { key, ...rest } = migrated.state; - - expect(rest).toEqual({ - description: null, - hide: 2, - label: 'constant', - name: 'constant', - skipUrlSync: true, - type: 'constant', - value: 'test', - }); - }); - - it('should migrate interval variable', () => { - const variable: IntervalVariableModel = { - name: 'intervalVar', - label: 'Interval Label', - type: 'interval', - rootStateKey: 'N4XLmH5Vz', - auto: false, - refresh: 2, - auto_count: 30, - auto_min: '10s', - current: { - selected: true, - text: '1m', - value: '1m', - }, - options: [ - { - selected: true, - text: '1m', - value: '1m', - }, - ], - query: '1m, 5m, 15m, 30m, 1h, 6h, 12h, 1d, 7d, 14d, 30d', - id: 'intervalVar', - global: false, - index: 4, - hide: 0, - skipUrlSync: false, - state: LoadingState.Done, - error: null, - description: null, - }; - - const migrated = createSceneVariableFromVariableModel(variable); - const { key, ...rest } = migrated.state; - expect(rest).toEqual({ - label: 'Interval Label', - autoEnabled: false, - autoMinInterval: '10s', - autoStepCount: 30, - description: null, - refresh: 2, - intervals: ['1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d', '7d', '14d', '30d'], - hide: 0, - name: 'intervalVar', - skipUrlSync: false, - type: 'interval', - value: '1m', - }); - }); - - it('should migrate textbox variable', () => { - const variable: TextBoxVariableModel = { - id: 'query0', - global: false, - index: 0, - state: LoadingState.Done, - error: null, - name: 'textboxVar', - label: 'Textbox Label', - description: 'Textbox Description', - type: 'textbox', - rootStateKey: 'N4XLmH5Vz', - current: {}, - hide: 0, - options: [], - query: 'defaultValue', - originalQuery: 'defaultValue', - skipUrlSync: false, - }; - - const migrated = createSceneVariableFromVariableModel(variable); - const { key, ...rest } = migrated.state; - expect(rest).toEqual({ - description: 'Textbox Description', - hide: 0, - label: 'Textbox Label', - name: 'textboxVar', - skipUrlSync: false, - type: 'textbox', - value: 'defaultValue', - }); - }); - - it('should migrate adhoc variable', () => { - const variable: TypedVariableModel = { - id: 'adhoc', - global: false, - index: 0, - state: LoadingState.Done, - error: null, - name: 'adhoc', - label: 'Adhoc Label', - description: 'Adhoc Description', - type: 'adhoc', - rootStateKey: 'N4XLmH5Vz', - datasource: { - uid: 'gdev-prometheus', - type: 'prometheus', - }, - filters: [ - { - key: 'filterTest', - operator: '=', - value: 'test', - }, - ], - baseFilters: [ - { - key: 'baseFilterTest', - operator: '=', - value: 'test', - }, - ], - hide: 0, - skipUrlSync: false, - }; - - const migrated = createSceneVariableFromVariableModel(variable) as AdHocFiltersVariable; - const filterVarState = migrated.state; - - expect(migrated).toBeInstanceOf(AdHocFiltersVariable); - expect(filterVarState).toEqual({ - key: expect.any(String), - description: 'Adhoc Description', - hide: 0, - label: 'Adhoc Label', - name: 'adhoc', - skipUrlSync: false, - type: 'adhoc', - filterExpression: 'filterTest="test"', - filters: [{ key: 'filterTest', operator: '=', value: 'test' }], - baseFilters: [{ key: 'baseFilterTest', operator: '=', value: 'test' }], - datasource: { uid: 'gdev-prometheus', type: 'prometheus' }, - applyMode: 'auto', - useQueriesAsFilterForOptions: true, - }); - }); - - it('should migrate adhoc variable with default keys', () => { - const variable: TypedVariableModel = { - id: 'adhoc', - global: false, - index: 0, - state: LoadingState.Done, - error: null, - name: 'adhoc', - label: 'Adhoc Label', - description: 'Adhoc Description', - type: 'adhoc', - rootStateKey: 'N4XLmH5Vz', - datasource: { - uid: 'gdev-prometheus', - type: 'prometheus', - }, - filters: [ - { - key: 'filterTest', - operator: '=', - value: 'test', - }, - ], - baseFilters: [ - { - key: 'baseFilterTest', - operator: '=', - value: 'test', - }, - ], - defaultKeys: [ - { - text: 'some', - value: '1', - }, - { - text: 'static', - value: '2', - }, - { - text: 'keys', - value: '3', - }, - ], - hide: 0, - skipUrlSync: false, - }; - - const migrated = createSceneVariableFromVariableModel(variable) as AdHocFiltersVariable; - const filterVarState = migrated.state; - - expect(migrated).toBeInstanceOf(AdHocFiltersVariable); - expect(filterVarState).toEqual({ - key: expect.any(String), - description: 'Adhoc Description', - hide: 0, - label: 'Adhoc Label', - name: 'adhoc', - skipUrlSync: false, - type: 'adhoc', - filterExpression: 'filterTest="test"', - filters: [{ key: 'filterTest', operator: '=', value: 'test' }], - baseFilters: [{ key: 'baseFilterTest', operator: '=', value: 'test' }], - datasource: { uid: 'gdev-prometheus', type: 'prometheus' }, - applyMode: 'auto', - defaultKeys: [ - { - text: 'some', - value: '1', - }, - { - text: 'static', - value: '2', - }, - { - text: 'keys', - value: '3', - }, - ], - useQueriesAsFilterForOptions: true, - }); - }); - - describe('when groupByVariable feature toggle is enabled', () => { - beforeAll(() => { - config.featureToggles.groupByVariable = true; - }); - - afterAll(() => { - config.featureToggles.groupByVariable = false; - }); - - it('should migrate groupby variable', () => { - const variable: GroupByVariableModel = { - id: 'groupby', - global: false, - index: 0, - state: LoadingState.Done, - error: null, - name: 'groupby', - label: 'GroupBy Label', - description: 'GroupBy Description', - type: 'groupby', - rootStateKey: 'N4XLmH5Vz', - datasource: { - uid: 'gdev-prometheus', - type: 'prometheus', - }, - multi: true, - options: [ - { - selected: false, - text: 'Foo', - value: 'foo', - }, - { - selected: false, - text: 'Bar', - value: 'bar', - }, - ], - current: {}, - query: '', - hide: 0, - skipUrlSync: false, - }; - - const migrated = createSceneVariableFromVariableModel(variable) as GroupByVariable; - const groupbyVarState = migrated.state; - - expect(migrated).toBeInstanceOf(GroupByVariable); - expect(groupbyVarState).toEqual({ - key: expect.any(String), - description: 'GroupBy Description', - hide: 0, - defaultOptions: [ - { - selected: false, - text: 'Foo', - value: 'foo', - }, - { - selected: false, - text: 'Bar', - value: 'bar', - }, - ], - isMulti: true, - layout: 'horizontal', - noValueOnClear: true, - label: 'GroupBy Label', - name: 'groupby', - skipUrlSync: false, - type: 'groupby', - baseFilters: [], - options: [], - text: [], - value: [], - datasource: { uid: 'gdev-prometheus', type: 'prometheus' }, - applyMode: 'auto', - }); - }); - }); - - describe('when groupByVariable feature toggle is disabled', () => { - it('should not migrate groupby variable and throw an error instead', () => { - const variable: GroupByVariableModel = { - id: 'groupby', - global: false, - index: 0, - state: LoadingState.Done, - error: null, - name: 'groupby', - label: 'GroupBy Label', - description: 'GroupBy Description', - type: 'groupby', - rootStateKey: 'N4XLmH5Vz', - datasource: { - uid: 'gdev-prometheus', - type: 'prometheus', - }, - multi: true, - options: [], - current: {}, - query: '', - hide: 0, - skipUrlSync: false, - }; - - expect(() => createSceneVariableFromVariableModel(variable)).toThrow('Scenes: Unsupported variable type'); - }); - }); - - it.each(['system'])('should throw for unsupported (yet) variables', (type) => { - const variable = { - name: 'query0', - type: type as VariableType, - }; - - expect(() => createSceneVariableFromVariableModel(variable as TypedVariableModel)).toThrow(); - }); - - it('should handle variable without current', () => { - // @ts-expect-error - const variable: TypedVariableModel = { - id: 'query1', - name: 'query1', - type: 'datasource', - global: false, - regex: '/^gdev/', - options: [], - query: 'prometheus', - multi: true, - includeAll: true, - refresh: 1, - allValue: 'Custom all', - }; - - const migrated = createSceneVariableFromVariableModel(variable); - const { key, ...rest } = migrated.state; - - expect(migrated).toBeInstanceOf(DataSourceVariable); - expect(rest).toEqual({ - allValue: 'Custom all', - defaultToAll: true, - includeAll: true, - label: undefined, - name: 'query1', - options: [], - pluginId: 'prometheus', - regex: '/^gdev/', - text: '', - type: 'datasource', - value: '', - isMulti: true, - }); - }); - }); - describe('Repeating rows', () => { it('Should build correct scene model', () => { const scene = transformSaveModelToScene({ dashboard: repeatingRowsAndPanelsDashboardJson as any, meta: {} }); diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index d03b55af774..54fe957d097 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -1,6 +1,6 @@ import { uniqueId } from 'lodash'; -import { DataFrameDTO, DataFrameJSON, TypedVariableModel } from '@grafana/data'; +import { DataFrameDTO, DataFrameJSON } from '@grafana/data'; import { config } from '@grafana/runtime'; import { VizPanel, @@ -10,12 +10,6 @@ import { SceneTimeRange, SceneVariableSet, VariableValueSelectors, - SceneVariable, - CustomVariable, - DataSourceVariable, - QueryVariable, - ConstantVariable, - IntervalVariable, SceneRefreshPicker, SceneObject, VizPanelMenu, @@ -24,10 +18,7 @@ import { SceneGridItemLike, SceneDataLayerProvider, SceneDataLayerControls, - TextBoxVariable, UserActionEvent, - GroupByVariable, - AdHocFiltersVariable, sceneGraph, } from '@grafana/scenes'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; @@ -52,12 +43,8 @@ import { setDashboardPanelContext } from '../scene/setDashboardPanelContext'; import { createPanelDataProvider } from '../utils/createPanelDataProvider'; import { preserveDashboardSceneStateInLocalStorage } from '../utils/dashboardSessionState'; import { DashboardInteractions } from '../utils/interactions'; -import { - getCurrentValueForOldIntervalModel, - getDashboardSceneFor, - getIntervalsFromQueryString, - getVizPanelKeyForPanelId, -} from '../utils/utils'; +import { getDashboardSceneFor, getVizPanelKeyForPanelId } from '../utils/utils'; +import { createVariablesForDashboard, createVariablesForSnapshot } from '../utils/variables'; import { getAngularPanelMigrationHandler } from './angularMigration'; import { GRAFANA_DATASOURCE_REF } from './const'; @@ -198,22 +185,11 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, let alertStatesLayer: AlertStatesDataLayer | undefined; if (oldModel.templating?.list?.length) { - const variableObjects = oldModel.templating.list - .map((v) => { - try { - return createSceneVariableFromVariableModel(v); - } catch (err) { - console.error(err); - return null; - } - }) - // TODO: Remove filter - // Added temporarily to allow skipping non-compatible variables - .filter((v): v is SceneVariable => Boolean(v)); - - variables = new SceneVariableSet({ - variables: variableObjects, - }); + if (oldModel.meta.isSnapshot) { + variables = createVariablesForSnapshot(oldModel); + } else { + variables = createVariablesForDashboard(oldModel); + } } else { // Create empty variable set variables = new SceneVariableSet({ @@ -303,128 +279,6 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, return dashboardScene; } -export function createSceneVariableFromVariableModel(variable: TypedVariableModel): SceneVariable { - const commonProperties = { - name: variable.name, - label: variable.label, - description: variable.description, - }; - if (variable.type === 'adhoc') { - return new AdHocFiltersVariable({ - ...commonProperties, - description: variable.description, - skipUrlSync: variable.skipUrlSync, - hide: variable.hide, - datasource: variable.datasource, - applyMode: 'auto', - filters: variable.filters ?? [], - baseFilters: variable.baseFilters ?? [], - defaultKeys: variable.defaultKeys, - useQueriesAsFilterForOptions: true, - }); - } - if (variable.type === 'custom') { - return new CustomVariable({ - ...commonProperties, - value: variable.current?.value ?? '', - text: variable.current?.text ?? '', - - query: variable.query, - isMulti: variable.multi, - allValue: variable.allValue || undefined, - includeAll: variable.includeAll, - defaultToAll: Boolean(variable.includeAll), - skipUrlSync: variable.skipUrlSync, - hide: variable.hide, - }); - } else if (variable.type === 'query') { - return new QueryVariable({ - ...commonProperties, - value: variable.current?.value ?? '', - text: variable.current?.text ?? '', - - query: variable.query, - datasource: variable.datasource, - sort: variable.sort, - refresh: variable.refresh, - regex: variable.regex, - allValue: variable.allValue || undefined, - includeAll: variable.includeAll, - defaultToAll: Boolean(variable.includeAll), - isMulti: variable.multi, - skipUrlSync: variable.skipUrlSync, - hide: variable.hide, - definition: variable.definition, - }); - } else if (variable.type === 'datasource') { - return new DataSourceVariable({ - ...commonProperties, - value: variable.current?.value ?? '', - text: variable.current?.text ?? '', - regex: variable.regex, - pluginId: variable.query, - allValue: variable.allValue || undefined, - includeAll: variable.includeAll, - defaultToAll: Boolean(variable.includeAll), - skipUrlSync: variable.skipUrlSync, - isMulti: variable.multi, - hide: variable.hide, - }); - } else if (variable.type === 'interval') { - const intervals = getIntervalsFromQueryString(variable.query); - const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals); - return new IntervalVariable({ - ...commonProperties, - value: currentInterval, - intervals: intervals, - autoEnabled: variable.auto, - autoStepCount: variable.auto_count, - autoMinInterval: variable.auto_min, - refresh: variable.refresh, - skipUrlSync: variable.skipUrlSync, - hide: variable.hide, - }); - } else if (variable.type === 'constant') { - return new ConstantVariable({ - ...commonProperties, - value: variable.query, - skipUrlSync: variable.skipUrlSync, - hide: variable.hide, - }); - } else if (variable.type === 'textbox') { - let val; - if (!variable?.current?.value) { - val = variable.query; - } else { - if (typeof variable.current.value === 'string') { - val = variable.current.value; - } else { - val = variable.current.value[0]; - } - } - - return new TextBoxVariable({ - ...commonProperties, - value: val, - skipUrlSync: variable.skipUrlSync, - hide: variable.hide, - }); - } else if (config.featureToggles.groupByVariable && variable.type === 'groupby') { - return new GroupByVariable({ - ...commonProperties, - datasource: variable.datasource, - value: variable.current?.value || [], - text: variable.current?.text || [], - skipUrlSync: variable.skipUrlSync, - hide: variable.hide, - // @ts-expect-error - defaultOptions: variable.options, - }); - } else { - throw new Error(`Scenes: Unsupported variable type ${variable.type}`); - } -} - export function buildGridItemForLibPanel(panel: PanelModel) { if (!panel.libraryPanel) { return null; diff --git a/public/app/features/dashboard-scene/settings/variables/utils.ts b/public/app/features/dashboard-scene/settings/variables/utils.ts index c7f5fb69588..ab9bfb9fe00 100644 --- a/public/app/features/dashboard-scene/settings/variables/utils.ts +++ b/public/app/features/dashboard-scene/settings/variables/utils.ts @@ -36,7 +36,8 @@ interface EditableVariableConfig { editor: React.ComponentType; } -export type EditableVariableType = Exclude; +//exclude system variable type and snapshot variable type +export type EditableVariableType = Exclude; export function isEditableVariableType(type: VariableType): type is EditableVariableType { return type !== 'system'; diff --git a/public/app/features/dashboard-scene/utils/variables.test.ts b/public/app/features/dashboard-scene/utils/variables.test.ts new file mode 100644 index 00000000000..cc97261c95b --- /dev/null +++ b/public/app/features/dashboard-scene/utils/variables.test.ts @@ -0,0 +1,775 @@ +import { + ConstantVariableModel, + CustomVariableModel, + DataSourceVariableModel, + GroupByVariableModel, + IntervalVariableModel, + LoadingState, + QueryVariableModel, + TextBoxVariableModel, + TypedVariableModel, +} from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { + AdHocFiltersVariable, + CustomVariable, + DataSourceVariable, + GroupByVariable, + QueryVariable, + SceneVariableSet, +} from '@grafana/scenes'; +import { defaultDashboard, defaultTimePickerConfig, VariableType } from '@grafana/schema'; +import { DashboardModel } from 'app/features/dashboard/state'; + +import { SnapshotVariable } from '../serialization/custom-variables/SnapshotVariable'; +import { NEW_LINK } from '../settings/links/utils'; + +import { createSceneVariableFromVariableModel, createVariablesForSnapshot } from './variables'; + +describe('when creating variables objects', () => { + it('should migrate custom variable', () => { + const variable: CustomVariableModel = { + current: { + selected: false, + text: 'a', + value: 'a', + }, + hide: 0, + includeAll: false, + multi: false, + name: 'query0', + options: [ + { + selected: true, + text: 'a', + value: 'a', + }, + { + selected: false, + text: 'b', + value: 'b', + }, + { + selected: false, + text: 'c', + value: 'c', + }, + { + selected: false, + text: 'd', + value: 'd', + }, + ], + query: 'a,b,c,d', + skipUrlSync: false, + type: 'custom', + rootStateKey: 'N4XLmH5Vz', + id: 'query0', + global: false, + index: 0, + state: LoadingState.Done, + error: null, + description: null, + allValue: null, + }; + + const migrated = createSceneVariableFromVariableModel(variable); + const { key, ...rest } = migrated.state; + + expect(migrated).toBeInstanceOf(CustomVariable); + expect(rest).toEqual({ + allValue: undefined, + defaultToAll: false, + description: null, + includeAll: false, + isMulti: false, + label: undefined, + name: 'query0', + options: [], + query: 'a,b,c,d', + skipUrlSync: false, + text: 'a', + type: 'custom', + value: 'a', + hide: 0, + }); + }); + + it('should migrate query variable with definition', () => { + const variable: QueryVariableModel = { + allValue: null, + current: { + text: 'America', + value: 'America', + selected: false, + }, + datasource: { + uid: 'P15396BDD62B2BE29', + type: 'influxdb', + }, + definition: 'SHOW TAG VALUES WITH KEY = "datacenter"', + hide: 0, + includeAll: false, + label: 'Datacenter', + multi: false, + name: 'datacenter', + options: [ + { + text: 'America', + value: 'America', + selected: true, + }, + { + text: 'Africa', + value: 'Africa', + selected: false, + }, + { + text: 'Asia', + value: 'Asia', + selected: false, + }, + { + text: 'Europe', + value: 'Europe', + selected: false, + }, + ], + query: 'SHOW TAG VALUES WITH KEY = "datacenter" ', + refresh: 1, + regex: '', + skipUrlSync: false, + sort: 0, + type: 'query', + rootStateKey: '000000002', + id: 'datacenter', + global: false, + index: 0, + state: LoadingState.Done, + error: null, + description: null, + }; + + const migrated = createSceneVariableFromVariableModel(variable); + const { key, ...rest } = migrated.state; + + expect(migrated).toBeInstanceOf(QueryVariable); + expect(rest).toEqual({ + allValue: undefined, + datasource: { + type: 'influxdb', + uid: 'P15396BDD62B2BE29', + }, + defaultToAll: false, + description: null, + includeAll: false, + isMulti: false, + label: 'Datacenter', + name: 'datacenter', + options: [], + query: 'SHOW TAG VALUES WITH KEY = "datacenter" ', + refresh: 1, + regex: '', + skipUrlSync: false, + sort: 0, + text: 'America', + type: 'query', + value: 'America', + hide: 0, + definition: 'SHOW TAG VALUES WITH KEY = "datacenter"', + }); + }); + + it('should migrate datasource variable', () => { + const variable: DataSourceVariableModel = { + id: 'query1', + rootStateKey: 'N4XLmH5Vz', + name: 'query1', + type: 'datasource', + global: false, + index: 1, + hide: 0, + skipUrlSync: false, + state: LoadingState.Done, + error: null, + description: null, + current: { + value: ['gdev-prometheus', 'gdev-slow-prometheus'], + text: ['gdev-prometheus', 'gdev-slow-prometheus'], + selected: true, + }, + regex: '/^gdev/', + options: [ + { + text: 'All', + value: '$__all', + selected: false, + }, + { + text: 'gdev-prometheus', + value: 'gdev-prometheus', + selected: true, + }, + { + text: 'gdev-slow-prometheus', + value: 'gdev-slow-prometheus', + selected: false, + }, + ], + query: 'prometheus', + multi: true, + includeAll: true, + refresh: 1, + allValue: 'Custom all', + }; + + const migrated = createSceneVariableFromVariableModel(variable); + const { key, ...rest } = migrated.state; + + expect(migrated).toBeInstanceOf(DataSourceVariable); + expect(rest).toEqual({ + allValue: 'Custom all', + defaultToAll: true, + includeAll: true, + label: undefined, + name: 'query1', + options: [], + pluginId: 'prometheus', + regex: '/^gdev/', + skipUrlSync: false, + text: ['gdev-prometheus', 'gdev-slow-prometheus'], + type: 'datasource', + value: ['gdev-prometheus', 'gdev-slow-prometheus'], + isMulti: true, + description: null, + hide: 0, + }); + }); + + it('should migrate constant variable', () => { + const variable: ConstantVariableModel = { + hide: 2, + label: 'constant', + name: 'constant', + skipUrlSync: false, + type: 'constant', + rootStateKey: 'N4XLmH5Vz', + current: { + selected: true, + text: 'test', + value: 'test', + }, + options: [ + { + selected: true, + text: 'test', + value: 'test', + }, + ], + query: 'test', + id: 'constant', + global: false, + index: 3, + state: LoadingState.Done, + error: null, + description: null, + }; + + const migrated = createSceneVariableFromVariableModel(variable); + const { key, ...rest } = migrated.state; + + expect(rest).toEqual({ + description: null, + hide: 2, + label: 'constant', + name: 'constant', + skipUrlSync: true, + type: 'constant', + value: 'test', + }); + }); + + it('should migrate interval variable', () => { + const variable: IntervalVariableModel = { + name: 'intervalVar', + label: 'Interval Label', + type: 'interval', + rootStateKey: 'N4XLmH5Vz', + auto: false, + refresh: 2, + auto_count: 30, + auto_min: '10s', + current: { + selected: true, + text: '1m', + value: '1m', + }, + options: [ + { + selected: true, + text: '1m', + value: '1m', + }, + ], + query: '1m, 5m, 15m, 30m, 1h, 6h, 12h, 1d, 7d, 14d, 30d', + id: 'intervalVar', + global: false, + index: 4, + hide: 0, + skipUrlSync: false, + state: LoadingState.Done, + error: null, + description: null, + }; + + const migrated = createSceneVariableFromVariableModel(variable); + const { key, ...rest } = migrated.state; + expect(rest).toEqual({ + label: 'Interval Label', + autoEnabled: false, + autoMinInterval: '10s', + autoStepCount: 30, + description: null, + refresh: 2, + intervals: ['1m', '5m', '15m', '30m', '1h', '6h', '12h', '1d', '7d', '14d', '30d'], + hide: 0, + name: 'intervalVar', + skipUrlSync: false, + type: 'interval', + value: '1m', + }); + }); + + it('should migrate textbox variable', () => { + const variable: TextBoxVariableModel = { + id: 'query0', + global: false, + index: 0, + state: LoadingState.Done, + error: null, + name: 'textboxVar', + label: 'Textbox Label', + description: 'Textbox Description', + type: 'textbox', + rootStateKey: 'N4XLmH5Vz', + current: {}, + hide: 0, + options: [], + query: 'defaultValue', + originalQuery: 'defaultValue', + skipUrlSync: false, + }; + + const migrated = createSceneVariableFromVariableModel(variable); + const { key, ...rest } = migrated.state; + expect(rest).toEqual({ + description: 'Textbox Description', + hide: 0, + label: 'Textbox Label', + name: 'textboxVar', + skipUrlSync: false, + type: 'textbox', + value: 'defaultValue', + }); + }); + + it('should migrate adhoc variable', () => { + const variable: TypedVariableModel = { + id: 'adhoc', + global: false, + index: 0, + state: LoadingState.Done, + error: null, + name: 'adhoc', + label: 'Adhoc Label', + description: 'Adhoc Description', + type: 'adhoc', + rootStateKey: 'N4XLmH5Vz', + datasource: { + uid: 'gdev-prometheus', + type: 'prometheus', + }, + filters: [ + { + key: 'filterTest', + operator: '=', + value: 'test', + }, + ], + baseFilters: [ + { + key: 'baseFilterTest', + operator: '=', + value: 'test', + }, + ], + hide: 0, + skipUrlSync: false, + }; + + const migrated = createSceneVariableFromVariableModel(variable) as AdHocFiltersVariable; + const filterVarState = migrated.state; + + expect(migrated).toBeInstanceOf(AdHocFiltersVariable); + expect(filterVarState).toEqual({ + key: expect.any(String), + description: 'Adhoc Description', + hide: 0, + label: 'Adhoc Label', + name: 'adhoc', + skipUrlSync: false, + type: 'adhoc', + filterExpression: 'filterTest="test"', + filters: [{ key: 'filterTest', operator: '=', value: 'test' }], + baseFilters: [{ key: 'baseFilterTest', operator: '=', value: 'test' }], + datasource: { uid: 'gdev-prometheus', type: 'prometheus' }, + applyMode: 'auto', + useQueriesAsFilterForOptions: true, + }); + }); + + it('should migrate adhoc variable with default keys', () => { + const variable: TypedVariableModel = { + id: 'adhoc', + global: false, + index: 0, + state: LoadingState.Done, + error: null, + name: 'adhoc', + label: 'Adhoc Label', + description: 'Adhoc Description', + type: 'adhoc', + rootStateKey: 'N4XLmH5Vz', + datasource: { + uid: 'gdev-prometheus', + type: 'prometheus', + }, + filters: [ + { + key: 'filterTest', + operator: '=', + value: 'test', + }, + ], + baseFilters: [ + { + key: 'baseFilterTest', + operator: '=', + value: 'test', + }, + ], + defaultKeys: [ + { + text: 'some', + value: '1', + }, + { + text: 'static', + value: '2', + }, + { + text: 'keys', + value: '3', + }, + ], + hide: 0, + skipUrlSync: false, + }; + + const migrated = createSceneVariableFromVariableModel(variable) as AdHocFiltersVariable; + const filterVarState = migrated.state; + + expect(migrated).toBeInstanceOf(AdHocFiltersVariable); + expect(filterVarState).toEqual({ + key: expect.any(String), + description: 'Adhoc Description', + hide: 0, + label: 'Adhoc Label', + name: 'adhoc', + skipUrlSync: false, + type: 'adhoc', + filterExpression: 'filterTest="test"', + filters: [{ key: 'filterTest', operator: '=', value: 'test' }], + baseFilters: [{ key: 'baseFilterTest', operator: '=', value: 'test' }], + datasource: { uid: 'gdev-prometheus', type: 'prometheus' }, + applyMode: 'auto', + defaultKeys: [ + { + text: 'some', + value: '1', + }, + { + text: 'static', + value: '2', + }, + { + text: 'keys', + value: '3', + }, + ], + useQueriesAsFilterForOptions: true, + }); + }); + + describe('when groupByVariable feature toggle is enabled', () => { + beforeAll(() => { + config.featureToggles.groupByVariable = true; + }); + + afterAll(() => { + config.featureToggles.groupByVariable = false; + }); + + it('should migrate groupby variable', () => { + const variable: GroupByVariableModel = { + id: 'groupby', + global: false, + index: 0, + state: LoadingState.Done, + error: null, + name: 'groupby', + label: 'GroupBy Label', + description: 'GroupBy Description', + type: 'groupby', + rootStateKey: 'N4XLmH5Vz', + datasource: { + uid: 'gdev-prometheus', + type: 'prometheus', + }, + multi: true, + options: [ + { + selected: false, + text: 'Foo', + value: 'foo', + }, + { + selected: false, + text: 'Bar', + value: 'bar', + }, + ], + current: {}, + query: '', + hide: 0, + skipUrlSync: false, + }; + + const migrated = createSceneVariableFromVariableModel(variable) as GroupByVariable; + const groupbyVarState = migrated.state; + + expect(migrated).toBeInstanceOf(GroupByVariable); + expect(groupbyVarState).toEqual({ + key: expect.any(String), + description: 'GroupBy Description', + hide: 0, + defaultOptions: [ + { + selected: false, + text: 'Foo', + value: 'foo', + }, + { + selected: false, + text: 'Bar', + value: 'bar', + }, + ], + isMulti: true, + layout: 'horizontal', + noValueOnClear: true, + label: 'GroupBy Label', + name: 'groupby', + skipUrlSync: false, + type: 'groupby', + baseFilters: [], + options: [], + text: [], + value: [], + datasource: { uid: 'gdev-prometheus', type: 'prometheus' }, + applyMode: 'auto', + }); + }); + }); + + describe('when groupByVariable feature toggle is disabled', () => { + it('should not migrate groupby variable and throw an error instead', () => { + const variable: GroupByVariableModel = { + id: 'groupby', + global: false, + index: 0, + state: LoadingState.Done, + error: null, + name: 'groupby', + label: 'GroupBy Label', + description: 'GroupBy Description', + type: 'groupby', + rootStateKey: 'N4XLmH5Vz', + datasource: { + uid: 'gdev-prometheus', + type: 'prometheus', + }, + multi: true, + options: [], + current: {}, + query: '', + hide: 0, + skipUrlSync: false, + }; + + expect(() => createSceneVariableFromVariableModel(variable)).toThrow('Scenes: Unsupported variable type'); + }); + }); + + it.each(['system'])('should throw for unsupported (yet) variables', (type) => { + const variable = { + name: 'query0', + type: type as VariableType, + }; + + expect(() => createSceneVariableFromVariableModel(variable as TypedVariableModel)).toThrow(); + }); + + it('should handle variable without current', () => { + // @ts-expect-error + const variable: TypedVariableModel = { + id: 'query1', + name: 'query1', + type: 'datasource', + global: false, + regex: '/^gdev/', + options: [], + query: 'prometheus', + multi: true, + includeAll: true, + refresh: 1, + allValue: 'Custom all', + }; + + const migrated = createSceneVariableFromVariableModel(variable); + const { key, ...rest } = migrated.state; + + expect(migrated).toBeInstanceOf(DataSourceVariable); + expect(rest).toEqual({ + allValue: 'Custom all', + defaultToAll: true, + includeAll: true, + label: undefined, + name: 'query1', + options: [], + pluginId: 'prometheus', + regex: '/^gdev/', + text: '', + type: 'datasource', + value: '', + isMulti: true, + }); + }); +}); + +describe('when creating snapshot variables from dashboard model', () => { + it('should create SnapshotVariables when required', () => { + const customVariable = { + current: { + selected: false, + text: 'a', + value: 'a', + }, + hide: 0, + includeAll: false, + multi: false, + name: 'custom0', + options: [], + query: 'a,b,c,d', + skipUrlSync: false, + type: 'custom' as VariableType, + rootStateKey: 'N4XLmH5Vz', + }; + + const intervalVariable = { + current: { + selected: false, + text: '10s', + value: '10s', + }, + hide: 0, + includeAll: false, + multi: false, + name: 'interval0', + options: [], + query: '10s,20s,30s', + skipUrlSync: false, + type: 'interval' as VariableType, + rootStateKey: 'N4XLmH5Vz', + }; + + const adHocVariable = { + global: false, + name: 'CoolFilters', + label: 'CoolFilters Label', + type: 'adhoc' as VariableType, + datasource: { + uid: 'gdev-prometheus', + type: 'prometheus', + }, + filters: [ + { + key: 'filterTest', + operator: '=', + value: 'test', + }, + ], + baseFilters: [ + { + key: 'baseFilterTest', + operator: '=', + value: 'test', + }, + ], + hide: 0, + index: 0, + }; + + const snapshot = { + ...defaultDashboard, + title: 'snapshot dash', + uid: 'test-uid', + time: { from: 'now-10h', to: 'now' }, + weekStart: 'saturday', + fiscalYearStartMonth: 2, + timezone: 'America/New_York', + timepicker: { + ...defaultTimePickerConfig, + hidden: true, + }, + links: [{ ...NEW_LINK, title: 'Link 1' }], + templating: { + list: [customVariable, adHocVariable, intervalVariable], + }, + }; + + const oldModel = new DashboardModel(snapshot, { isSnapshot: true }); + const variables = createVariablesForSnapshot(oldModel); + + // check variables were converted to snapshot variables + expect(variables).toBeInstanceOf(SceneVariableSet); + expect(variables.getByName('custom0')).toBeInstanceOf(SnapshotVariable); + expect(variables?.getByName('CoolFilters')).toBeInstanceOf(AdHocFiltersVariable); + expect(variables?.getByName('interval0')).toBeInstanceOf(SnapshotVariable); + // // custom snapshot + const customSnapshot = variables?.getByName('custom0') as SnapshotVariable; + expect(customSnapshot.state.value).toBe('a'); + expect(customSnapshot.state.text).toBe('a'); + expect(customSnapshot.state.isReadOnly).toBe(true); + // // adhoc snapshot + const adhocSnapshot = variables?.getByName('CoolFilters') as AdHocFiltersVariable; + expect(adhocSnapshot.state.filters).toEqual(adHocVariable.filters); + expect(adhocSnapshot.state.readOnly).toBe(true); + // + // // interval snapshot + const intervalSnapshot = variables?.getByName('interval0') as SnapshotVariable; + expect(intervalSnapshot.state.value).toBe('10s'); + expect(intervalSnapshot.state.text).toBe('10s'); + expect(intervalSnapshot.state.isReadOnly).toBe(true); + }); +}); diff --git a/public/app/features/dashboard-scene/utils/variables.ts b/public/app/features/dashboard-scene/utils/variables.ts new file mode 100644 index 00000000000..3eefdc84fc3 --- /dev/null +++ b/public/app/features/dashboard-scene/utils/variables.ts @@ -0,0 +1,238 @@ +import { TypedVariableModel } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { + AdHocFiltersVariable, + ConstantVariable, + CustomVariable, + DataSourceVariable, + GroupByVariable, + IntervalVariable, + QueryVariable, + SceneVariable, + SceneVariableSet, + TextBoxVariable, +} from '@grafana/scenes'; +import { DashboardModel } from 'app/features/dashboard/state'; + +import { SnapshotVariable } from '../serialization/custom-variables/SnapshotVariable'; + +import { getCurrentValueForOldIntervalModel, getIntervalsFromQueryString } from './utils'; + +export function createVariablesForDashboard(oldModel: DashboardModel) { + const variableObjects = oldModel.templating.list + .map((v) => { + try { + return createSceneVariableFromVariableModel(v); + } catch (err) { + console.error(err); + return null; + } + }) + // TODO: Remove filter + // Added temporarily to allow skipping non-compatible variables + .filter((v): v is SceneVariable => Boolean(v)); + + return new SceneVariableSet({ + variables: variableObjects, + }); +} + +export function createVariablesForSnapshot(oldModel: DashboardModel) { + const variableObjects = oldModel.templating.list + .map((v) => { + try { + // for adhoc we are using the AdHocFiltersVariable from scenes becuase of its complexity + if (v.type === 'adhoc') { + return new AdHocFiltersVariable({ + name: v.name, + label: v.label, + readOnly: true, + description: v.description, + skipUrlSync: v.skipUrlSync, + hide: v.hide, + datasource: v.datasource, + applyMode: 'auto', + filters: v.filters ?? [], + baseFilters: v.baseFilters ?? [], + defaultKeys: v.defaultKeys, + useQueriesAsFilterForOptions: true, + }); + } + // for other variable types we are using the SnapshotVariable + return createSnapshotVariable(v); + } catch (err) { + console.error(err); + return null; + } + }) + // TODO: Remove filter + // Added temporarily to allow skipping non-compatible variables + .filter((v): v is SceneVariable => Boolean(v)); + + return new SceneVariableSet({ + variables: variableObjects, + }); +} + +/** Snapshots variables are read-only and should not be updated */ +export function createSnapshotVariable(variable: TypedVariableModel): SceneVariable { + let snapshotVariable: SnapshotVariable; + let current: { value: string | string[]; text: string | string[] }; + if (variable.type === 'interval') { + const intervals = getIntervalsFromQueryString(variable.query); + const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals); + snapshotVariable = new SnapshotVariable({ + name: variable.name, + label: variable.label, + description: variable.description, + value: currentInterval, + text: currentInterval, + hide: variable.hide, + }); + return snapshotVariable; + } + + if (variable.type === 'system' || variable.type === 'constant' || variable.type === 'adhoc') { + current = { + value: '', + text: '', + }; + } else { + current = { + value: variable.current?.value ?? '', + text: variable.current?.text ?? '', + }; + } + + snapshotVariable = new SnapshotVariable({ + name: variable.name, + label: variable.label, + description: variable.description, + value: current?.value ?? '', + text: current?.text ?? '', + hide: variable.hide, + }); + return snapshotVariable; +} + +export function createSceneVariableFromVariableModel(variable: TypedVariableModel): SceneVariable { + const commonProperties = { + name: variable.name, + label: variable.label, + description: variable.description, + }; + if (variable.type === 'adhoc') { + return new AdHocFiltersVariable({ + ...commonProperties, + description: variable.description, + skipUrlSync: variable.skipUrlSync, + hide: variable.hide, + datasource: variable.datasource, + applyMode: 'auto', + filters: variable.filters ?? [], + baseFilters: variable.baseFilters ?? [], + defaultKeys: variable.defaultKeys, + useQueriesAsFilterForOptions: true, + }); + } + if (variable.type === 'custom') { + return new CustomVariable({ + ...commonProperties, + value: variable.current?.value ?? '', + text: variable.current?.text ?? '', + + query: variable.query, + isMulti: variable.multi, + allValue: variable.allValue || undefined, + includeAll: variable.includeAll, + defaultToAll: Boolean(variable.includeAll), + skipUrlSync: variable.skipUrlSync, + hide: variable.hide, + }); + } else if (variable.type === 'query') { + return new QueryVariable({ + ...commonProperties, + value: variable.current?.value ?? '', + text: variable.current?.text ?? '', + + query: variable.query, + datasource: variable.datasource, + sort: variable.sort, + refresh: variable.refresh, + regex: variable.regex, + allValue: variable.allValue || undefined, + includeAll: variable.includeAll, + defaultToAll: Boolean(variable.includeAll), + isMulti: variable.multi, + skipUrlSync: variable.skipUrlSync, + hide: variable.hide, + definition: variable.definition, + }); + } else if (variable.type === 'datasource') { + return new DataSourceVariable({ + ...commonProperties, + value: variable.current?.value ?? '', + text: variable.current?.text ?? '', + regex: variable.regex, + pluginId: variable.query, + allValue: variable.allValue || undefined, + includeAll: variable.includeAll, + defaultToAll: Boolean(variable.includeAll), + skipUrlSync: variable.skipUrlSync, + isMulti: variable.multi, + hide: variable.hide, + }); + } else if (variable.type === 'interval') { + const intervals = getIntervalsFromQueryString(variable.query); + const currentInterval = getCurrentValueForOldIntervalModel(variable, intervals); + return new IntervalVariable({ + ...commonProperties, + value: currentInterval, + intervals: intervals, + autoEnabled: variable.auto, + autoStepCount: variable.auto_count, + autoMinInterval: variable.auto_min, + refresh: variable.refresh, + skipUrlSync: variable.skipUrlSync, + hide: variable.hide, + }); + } else if (variable.type === 'constant') { + return new ConstantVariable({ + ...commonProperties, + value: variable.query, + skipUrlSync: variable.skipUrlSync, + hide: variable.hide, + }); + } else if (variable.type === 'textbox') { + let val; + if (!variable?.current?.value) { + val = variable.query; + } else { + if (typeof variable.current.value === 'string') { + val = variable.current.value; + } else { + val = variable.current.value[0]; + } + } + + return new TextBoxVariable({ + ...commonProperties, + value: val, + skipUrlSync: variable.skipUrlSync, + hide: variable.hide, + }); + } else if (config.featureToggles.groupByVariable && variable.type === 'groupby') { + return new GroupByVariable({ + ...commonProperties, + datasource: variable.datasource, + value: variable.current?.value || [], + text: variable.current?.text || [], + skipUrlSync: variable.skipUrlSync, + hide: variable.hide, + // @ts-expect-error + defaultOptions: variable.options, + }); + } else { + throw new Error(`Scenes: Unsupported variable type ${variable.type}`); + } +} diff --git a/public/app/features/variables/guard.test.ts b/public/app/features/variables/guard.test.ts index cd65f21e621..7e57f8422ad 100644 --- a/public/app/features/variables/guard.test.ts +++ b/public/app/features/variables/guard.test.ts @@ -23,6 +23,7 @@ import { createIntervalVariable, createOrgVariable, createQueryVariable, + createSnapshotVariable, createTextBoxVariable, createUserVariable, } from './state/__tests__/fixtures'; @@ -163,18 +164,19 @@ describe('type guards', () => { type ExtraVariableTypes = 'org' | 'dashboard' | 'user'; // prettier-ignore const variableFactsObj: Record = { - query: { variable: createQueryVariable(), isMulti: true, hasOptions: true, hasCurrent: true }, - adhoc: { variable: createAdhocVariable(), isMulti: false, hasOptions: false, hasCurrent: false }, - groupby: { variable: createGroupByVariable(), isMulti: true, hasOptions: true, hasCurrent: true }, - constant: { variable: createConstantVariable(), isMulti: false, hasOptions: true, hasCurrent: true }, - datasource: { variable: createDatasourceVariable(), isMulti: true, hasOptions: true, hasCurrent: true }, - interval: { variable: createIntervalVariable(), isMulti: false, hasOptions: true, hasCurrent: true }, - textbox: { variable: createTextBoxVariable(), isMulti: false, hasOptions: true, hasCurrent: true }, - system: { variable: createUserVariable(), isMulti: false, hasOptions: false, hasCurrent: true }, - user: { variable: createUserVariable(), isMulti: false, hasOptions: false, hasCurrent: true }, - org: { variable: createOrgVariable(), isMulti: false, hasOptions: false, hasCurrent: true }, - dashboard: { variable: createDashboardVariable(), isMulti: false, hasOptions: false, hasCurrent: true }, - custom: { variable: createCustomVariable(), isMulti: true, hasOptions: true, hasCurrent: true }, + query: { variable: createQueryVariable(), isMulti: true, hasOptions: true, hasCurrent: true }, + adhoc: { variable: createAdhocVariable(), isMulti: false, hasOptions: false, hasCurrent: false }, + groupby: { variable: createGroupByVariable(), isMulti: true, hasOptions: true, hasCurrent: true }, + constant: { variable: createConstantVariable(), isMulti: false, hasOptions: true, hasCurrent: true }, + datasource: { variable: createDatasourceVariable(), isMulti: true, hasOptions: true, hasCurrent: true }, + interval: { variable: createIntervalVariable(), isMulti: false, hasOptions: true, hasCurrent: true }, + textbox: { variable: createTextBoxVariable(), isMulti: false, hasOptions: true, hasCurrent: true }, + system: { variable: createUserVariable(), isMulti: false, hasOptions: false, hasCurrent: true }, + user: { variable: createUserVariable(), isMulti: false, hasOptions: false, hasCurrent: true }, + org: { variable: createOrgVariable(), isMulti: false, hasOptions: false, hasCurrent: true }, + dashboard: { variable: createDashboardVariable(), isMulti: false, hasOptions: false, hasCurrent: true }, + custom: { variable: createCustomVariable(), isMulti: true, hasOptions: true, hasCurrent: true }, + snapshot: { variable: createSnapshotVariable(), isMulti: false, hasOptions: true, hasCurrent: true }, }; const variableFacts = Object.values(variableFactsObj); diff --git a/public/app/features/variables/state/__tests__/fixtures.ts b/public/app/features/variables/state/__tests__/fixtures.ts index d30052bc886..f72326cd79b 100644 --- a/public/app/features/variables/state/__tests__/fixtures.ts +++ b/public/app/features/variables/state/__tests__/fixtures.ts @@ -10,6 +10,7 @@ import { LoadingState, OrgVariableModel, QueryVariableModel, + SnapshotVariableModel, TextBoxVariableModel, UserVariableModel, VariableHide, @@ -198,3 +199,13 @@ export function createCustomVariable(input: Partial = {}): ...input, }; } + +export function createSnapshotVariable(input: Partial = {}): SnapshotVariableModel { + return { + ...createBaseVariableModel('snapshot'), + query: '', + current: createVariableOption('prom-prod', { text: 'Prometheus (main)', selected: true }), + options: [], + ...input, + }; +} diff --git a/public/app/plugins/panel/logs/panelcfg.cue b/public/app/plugins/panel/logs/panelcfg.cue index d66b2d7c07b..f843bdb5706 100644 --- a/public/app/plugins/panel/logs/panelcfg.cue +++ b/public/app/plugins/panel/logs/panelcfg.cue @@ -41,8 +41,8 @@ composableKinds: PanelCfg: { isFilterLabelActive?: _ onClickFilterString?: _ onClickFilterOutString?: _ - onClickShowField?: _ - onClickHideField?: _ + onClickShowField?: _ + onClickHideField?: _ displayedFields?: [...string] } @cuetsy(kind="interface") } From eecd56659c4f3512b27cb6d741540fe67ae1b069 Mon Sep 17 00:00:00 2001 From: linoman <2051016+linoman@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:31:13 +0200 Subject: [PATCH 171/229] LDAP: SSO Configuration page (#91875) * Init screen for LDAP UI * add ldap drawer * update routes * update definitions * add definitions for ldap configurations * improve readibility * remove whitespace * clean up * Adjust LdapSettingsPage * adjust form autocomplete from backend call --- .../blocks/auth/authentik/ldap_authentik.toml | 2 +- pkg/services/ldap/settings.go | 6 +- .../features/admin/ldap/LdapSettingsPage.tsx | 294 ++++++++++++++++++ .../auth-config/AuthProvidersListPage.tsx | 2 +- public/app/routes/routes.tsx | 6 +- public/app/types/ldap.ts | 61 ++++ public/locales/en-US/grafana.json | 47 +++ public/locales/pseudo-LOCALE/grafana.json | 47 +++ 8 files changed, 457 insertions(+), 8 deletions(-) create mode 100644 public/app/features/admin/ldap/LdapSettingsPage.tsx diff --git a/devenv/docker/blocks/auth/authentik/ldap_authentik.toml b/devenv/docker/blocks/auth/authentik/ldap_authentik.toml index b8a627a9a44..69aadd8fac5 100644 --- a/devenv/docker/blocks/auth/authentik/ldap_authentik.toml +++ b/devenv/docker/blocks/auth/authentik/ldap_authentik.toml @@ -18,7 +18,7 @@ name = "displayName" surname = "sn" username = "cn" member_of = "memberOf" -email = "mail" +email = "mail" # Map ldap groups to grafana org roles [[servers.group_mappings]] diff --git a/pkg/services/ldap/settings.go b/pkg/services/ldap/settings.go index 66c943bddce..f3c38d266a7 100644 --- a/pkg/services/ldap/settings.go +++ b/pkg/services/ldap/settings.go @@ -114,11 +114,7 @@ func GetLDAPConfig(cfg *setting.Cfg) *Config { // GetConfig returns the LDAP config if LDAP is enabled otherwise it returns nil. It returns either cached value of // the config or it reads it and caches it first. func GetConfig(cfg *Config) (*ServersConfig, error) { - if cfg != nil { - if !cfg.Enabled { - return nil, nil - } - } else if !cfg.Enabled { + if cfg == nil || !cfg.Enabled { return nil, nil } diff --git a/public/app/features/admin/ldap/LdapSettingsPage.tsx b/public/app/features/admin/ldap/LdapSettingsPage.tsx new file mode 100644 index 00000000000..e0217d3c950 --- /dev/null +++ b/public/app/features/admin/ldap/LdapSettingsPage.tsx @@ -0,0 +1,294 @@ +import { css } from '@emotion/css'; +import { useEffect, useState } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; +import { connect } from 'react-redux'; + +import { AppEvents, GrafanaTheme2, NavModelItem } from '@grafana/data'; +import { getBackendSrv, getAppEvents } from '@grafana/runtime'; +import { useStyles2, Alert, Box, Button, Field, Input, Stack, TextLink } from '@grafana/ui'; +import { Page } from 'app/core/components/Page/Page'; +import config from 'app/core/config'; +import { t, Trans } from 'app/core/internationalization'; +import { Loader } from 'app/features/plugins/admin/components/Loader'; +import { LdapPayload, StoreState } from 'app/types'; + +const appEvents = getAppEvents(); + +const mapStateToProps = (state: StoreState) => ({ + ldapSsoSettings: state.ldap.ldapSsoSettings, +}); + +const mapDispatchToProps = {}; + +const connector = connect(mapStateToProps, mapDispatchToProps); + +const pageNav: NavModelItem = { + text: 'LDAP', + icon: 'shield', + id: 'LDAP', +}; + +const emptySettings: LdapPayload = { + id: '', + provider: '', + source: '', + settings: { + activeSyncEnabled: false, + allowSignUp: false, + config: { + servers: [ + { + attributes: {}, + bind_dn: '', + bind_password: '', + client_cert: '', + client_key: '', + group_mappings: [], + group_search_base_dns: [], + group_search_filter: '', + group_search_filter_user_attribute: '', + host: '', + min_tls_version: '', + port: 389, + root_ca_cert: '', + search_base_dns: [], + search_filter: '', + skip_org_role_sync: false, + ssl_skip_verify: false, + start_tls: false, + timeout: 10, + tls_ciphers: [], + tls_skip_verify: false, + use_ssl: false, + }, + ], + }, + enabled: false, + skipOrgRoleSync: false, + syncCron: '', + }, +}; + +export const LdapSettingsPage = () => { + const [isLoading, setIsLoading] = useState(true); + + const methods = useForm({ defaultValues: emptySettings }); + const { getValues, handleSubmit, register, reset } = methods; + + const styles = useStyles2(getStyles); + + useEffect(() => { + async function init() { + const payload = await getBackendSrv().get('/api/v1/sso-settings/ldap'); + if (!payload || !payload.settings || !payload.settings.config) { + appEvents.publish({ + type: AppEvents.alertError.name, + payload: [t('ldap-settings-page.alert.error-fetching', 'Error fetching LDAP settings')], + }); + return; + } + + reset(payload); + setIsLoading(false); + } + init(); + }, [reset]); + + /** + * Display warning if the feature flag is disabled + */ + if (!config.featureToggles.ssoSettingsLDAP) { + return ( + + + This page is only accessible by enabling the ssoSettingsLDAP feature flag. + + + ); + } + + /** + * Save payload to the backend + * @param payload LdapPayload + */ + const putPayload = async (payload: LdapPayload) => { + try { + const result = await getBackendSrv().put('/api/v1/sso-settings/ldap', payload); + if (result) { + appEvents.publish({ + type: AppEvents.alertError.name, + payload: [t('ldap-settings-page.alert.error-saving', 'Error saving LDAP settings')], + }); + } + appEvents.publish({ + type: AppEvents.alertSuccess.name, + payload: [t('ldap-settings-page.alert.saved', 'LDAP settings saved')], + }); + } catch (error) { + appEvents.publish({ + type: AppEvents.alertError.name, + payload: [t('ldap-settings-page.alert.error-saving', 'Error saving LDAP settings')], + }); + } + }; + + const onErrors = () => { + appEvents.publish({ + type: AppEvents.alertError.name, + payload: [t('ldap-settings-page.alert.error-validate-form', 'Error validating LDAP settings')], + }); + }; + + /** + * Button's Actions + */ + const submitAndEnableLdapSettings = (payload: LdapPayload) => { + payload.settings.enabled = true; + putPayload(payload); + }; + const saveForm = () => { + putPayload(getValues()); + }; + const discardForm = async () => { + try { + setIsLoading(true); + await getBackendSrv().delete('/api/v1/sso-settings/ldap'); + const payload = await getBackendSrv().get('/api/v1/sso-settings/ldap'); + if (!payload || !payload.settings || !payload.settings.config) { + appEvents.publish({ + type: AppEvents.alertError.name, + payload: [t('ldap-settings-page.alert.error-update', 'Error updating LDAP settings')], + }); + return; + } + reset(payload); + } catch (error) { + appEvents.publish({ + type: AppEvents.alertError.name, + payload: [t('ldap-settings-page.alert.error-saving', 'Error saving LDAP settings')], + }); + } finally { + setIsLoading(false); + } + }; + + const documentation = ( + + documentation + + ); + const subTitle = ( + + The LDAP integration in Grafana allows your Grafana users to log in with their LDAP credentials. Find out more in + our {documentation}. + + ); + + return ( + + + +
+ {isLoading && } + {!isLoading && ( +
+

+ Basic Settings +

+ + + + + + + + + + + + + + + + + + + + + + +
+ )} + +
+
+
+ ); +}; + +function getStyles(theme: GrafanaTheme2) { + return { + form: css({ + width: theme.spacing(68), + }), + }; +} + +export default connector(LdapSettingsPage); diff --git a/public/app/features/auth-config/AuthProvidersListPage.tsx b/public/app/features/auth-config/AuthProvidersListPage.tsx index b3add89876d..9f0860bda70 100644 --- a/public/app/features/auth-config/AuthProvidersListPage.tsx +++ b/public/app/features/auth-config/AuthProvidersListPage.tsx @@ -52,7 +52,7 @@ export const AuthConfigPageUnconnected = ({ reportInteraction('authentication_ui_provider_clicked', { provider: providerType, enabled }); }; - // filter out saml from sso providers because it is already included in availableProviders + // filter out saml and ldap from sso providers because it is already included in availableProviders providers = providers.filter((p) => p.provider !== 'saml'); // temporarily remove LDAP until its configuration form is ready diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index a52aafa5eeb..8bf3abb4f6f 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -298,7 +298,11 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/admin/authentication/ldap', - component: LdapPage, + component: config.featureToggles.ssoSettingsLDAP + ? SafeDynamicImport( + () => import(/* webpackChunkName: "LdapSettingsPage" */ 'app/features/admin/ldap/LdapSettingsPage') + ) + : LdapPage, }, { path: '/admin/authentication/:provider', diff --git a/public/app/types/ldap.ts b/public/app/types/ldap.ts index eecc12fe01a..e4e9c6b6790 100644 --- a/public/app/types/ldap.ts +++ b/public/app/types/ldap.ts @@ -64,6 +64,46 @@ export interface LdapServerInfo { error: string; } +export interface GroupMapping { + group_dn?: string; + org_id?: number; + org_role?: string; + grafana_admin?: boolean; +} + +export interface LdapAttributes { + email?: string; + member_of?: string; + name?: string; + surname?: string; + username?: string; +} + +export interface LdapServerConfig { + attributes: LdapAttributes; + bind_dn: string; + bind_password?: string; + client_cert: string; + client_key: string; + group_mappings: GroupMapping[]; + group_search_base_dns: string[]; + group_search_filter: string; + group_search_filter_user_attribute: string; + host: string; + min_tls_version: string; + port: number; + root_ca_cert: string; + search_base_dns: string[]; + search_filter: string; + skip_org_role_sync: boolean; + ssl_skip_verify: boolean; + start_tls: boolean; + timeout: number; + tls_ciphers: string[]; + tls_skip_verify: boolean; + use_ssl: boolean; +} + export type LdapConnectionInfo = LdapServerInfo[]; export interface LdapState { @@ -73,4 +113,25 @@ export interface LdapState { connectionError?: LdapError; userError?: LdapError; ldapError?: LdapError; + ldapSsoSettings?: LdapServerConfig; +} + +export interface LdapConfig { + servers: LdapServerConfig[]; +} + +export interface LdapSettings { + activeSyncEnabled: boolean; + allowSignUp: boolean; + config: LdapConfig; + enabled: boolean; + skipOrgRoleSync: boolean; + syncCron: string; +} + +export interface LdapPayload { + id: string; + provider: string; + settings: LdapSettings; + source: string; } diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index a0753371a30..45aff726dd2 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -977,6 +977,53 @@ "refresh": "Refresh" } }, + "ldap-settings-page": { + "alert": { + "error-fetching": "Error fetching LDAP settings", + "error-saving": "Error saving LDAP settings", + "error-update": "Error updating LDAP settings", + "error-validate-form": "Error validating LDAP settings", + "feature-flag-disabled": "This page is only accessible by enabling the <1>ssoSettingsLDAP feature flag.", + "saved": "LDAP settings saved" + }, + "bind-dn": { + "description": "Distinguished name of the account used to bind and authenticate to the LDAP server.", + "label": "Bind DN", + "placeholder": "example: cn=admin,dc=grafana,dc=org" + }, + "bind-password": { + "label": "Bind password" + }, + "buttons-section": { + "discard": { + "button": "Discard" + }, + "save": { + "button": "Save" + }, + "save-and-enable": { + "button": "Save and enable" + } + }, + "documentation": "documentation", + "host": { + "description": "Hostname or IP address of the LDAP server you wish to connect to.", + "label": "Server host", + "placeholder": "example: 127.0.0.1" + }, + "search_filter": { + "description": "LDAP search filter used to locate specific entries within the directory.", + "label": "Search filter*", + "placeholder": "example: cn=%s" + }, + "search-base-dns": { + "description": "An array of base dns to search through; separate by commas or spaces.", + "label": "Search base DNS *", + "placeholder": "example: \"dc=grafana.dc=org\"" + }, + "subtitle": "The LDAP integration in Grafana allows your Grafana users to log in with their LDAP credentials. Find out more in our {documentation}.", + "title": "Basic Settings" + }, "library-panel": { "add-modal": { "cancel": "Cancel", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index ac9ac502962..fe72cd0a0b5 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -977,6 +977,53 @@ "refresh": "Ŗęƒřęşĥ" } }, + "ldap-settings-page": { + "alert": { + "error-fetching": "Ēřřőř ƒęŧčĥįʼnģ ĿĐÅP şęŧŧįʼnģş", + "error-saving": "Ēřřőř şävįʼnģ ĿĐÅP şęŧŧįʼnģş", + "error-update": "Ēřřőř ūpđäŧįʼnģ ĿĐÅP şęŧŧįʼnģş", + "error-validate-form": "Ēřřőř väľįđäŧįʼnģ ĿĐÅP şęŧŧįʼnģş", + "feature-flag-disabled": "Ŧĥįş päģę įş őʼnľy äččęşşįþľę þy ęʼnäþľįʼnģ ŧĥę <1>şşőŜęŧŧįʼnģşĿĐÅP ƒęäŧūřę ƒľäģ.", + "saved": "ĿĐÅP şęŧŧįʼnģş şävęđ" + }, + "bind-dn": { + "description": "Đįşŧįʼnģūįşĥęđ ʼnämę őƒ ŧĥę äččőūʼnŧ ūşęđ ŧő þįʼnđ äʼnđ äūŧĥęʼnŧįčäŧę ŧő ŧĥę ĿĐÅP şęřvęř.", + "label": "ßįʼnđ ĐŃ", + "placeholder": "ęχämpľę: čʼn=äđmįʼn,đč=ģřäƒäʼnä,đč=őřģ" + }, + "bind-password": { + "label": "ßįʼnđ päşşŵőřđ" + }, + "buttons-section": { + "discard": { + "button": "Đįşčäřđ" + }, + "save": { + "button": "Ŝävę" + }, + "save-and-enable": { + "button": "Ŝävę äʼnđ ęʼnäþľę" + } + }, + "documentation": "đőčūmęʼnŧäŧįőʼn", + "host": { + "description": "Ħőşŧʼnämę őř ĨP äđđřęşş őƒ ŧĥę ĿĐÅP şęřvęř yőū ŵįşĥ ŧő čőʼnʼnęčŧ ŧő.", + "label": "Ŝęřvęř ĥőşŧ", + "placeholder": "ęχämpľę: 127.0.0.1" + }, + "search_filter": { + "description": "ĿĐÅP şęäřčĥ ƒįľŧęř ūşęđ ŧő ľőčäŧę şpęčįƒįč ęʼnŧřįęş ŵįŧĥįʼn ŧĥę đįřęčŧőřy.", + "label": "Ŝęäřčĥ ƒįľŧęř*", + "placeholder": "ęχämpľę: čʼn=%ş" + }, + "search-base-dns": { + "description": "Åʼn äřřäy őƒ þäşę đʼnş ŧő şęäřčĥ ŧĥřőūģĥ; şępäřäŧę þy čőmmäş őř şpäčęş.", + "label": "Ŝęäřčĥ þäşę ĐŃŜ *", + "placeholder": "ęχämpľę: \"đč=ģřäƒäʼnä.đč=őřģ\"" + }, + "subtitle": "Ŧĥę ĿĐÅP įʼnŧęģřäŧįőʼn įʼn Ğřäƒäʼnä äľľőŵş yőūř Ğřäƒäʼnä ūşęřş ŧő ľőģ įʼn ŵįŧĥ ŧĥęįř ĿĐÅP čřęđęʼnŧįäľş. Fįʼnđ őūŧ mőřę įʼn őūř {đőčūmęʼnŧäŧįőʼn}.", + "title": "ßäşįč Ŝęŧŧįʼnģş" + }, "library-panel": { "add-modal": { "cancel": "Cäʼnčęľ", From 15a623c344587d900f97282e98ce5508c95d973f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:04:25 +0000 Subject: [PATCH 172/229] Update dependency @playwright/test to v1.46.1 --- package.json | 2 +- yarn.lock | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 847f9901cce..3a4086d45c0 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "@grafana/plugin-e2e": "1.6.1", "@grafana/tsconfig": "^2.0.0", "@manypkg/get-packages": "^2.2.0", - "@playwright/test": "1.46.0", + "@playwright/test": "1.46.1", "@pmmmwh/react-refresh-webpack-plugin": "0.5.15", "@react-types/button": "3.9.6", "@react-types/menu": "3.9.11", diff --git a/yarn.lock b/yarn.lock index 5e11117a297..7bba332c4fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5793,14 +5793,14 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:1.46.0": - version: 1.46.0 - resolution: "@playwright/test@npm:1.46.0" +"@playwright/test@npm:1.46.1": + version: 1.46.1 + resolution: "@playwright/test@npm:1.46.1" dependencies: - playwright: "npm:1.46.0" + playwright: "npm:1.46.1" bin: playwright: cli.js - checksum: 10/710bf451555e67476bf6e911a07ec0e011474f769a10f8073b3b22fe9b81086a83b6821da354900a6d6d14d60e4320b2c9f7249cc5480e3923de56a8501b7ffe + checksum: 10/09e2c28574402f14e2d6f6843022c5778382dc7f703bae931dd531fc0fc1b725a862d3b52932bd6912cb13cbaed54822af33eb3d70134d93b0f1c10ec3fb0756 languageName: node linkType: hard @@ -18183,7 +18183,7 @@ __metadata: "@opentelemetry/api": "npm:1.9.0" "@opentelemetry/exporter-collector": "npm:0.25.0" "@opentelemetry/semantic-conventions": "npm:1.25.1" - "@playwright/test": "npm:1.46.0" + "@playwright/test": "npm:1.46.1" "@pmmmwh/react-refresh-webpack-plugin": "npm:0.5.15" "@popperjs/core": "npm:2.11.8" "@react-aria/dialog": "npm:3.5.17" @@ -25057,27 +25057,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.46.0": - version: 1.46.0 - resolution: "playwright-core@npm:1.46.0" +"playwright-core@npm:1.46.1": + version: 1.46.1 + resolution: "playwright-core@npm:1.46.1" bin: playwright-core: cli.js - checksum: 10/1fd237d01380be0d650ae7df73fb796eae9c208e0746bb110db270139f1d2a96bf3b8856c394a48720b30e145614a10f275ab08627d0c95ba2160dc0402a90cb + checksum: 10/950aa935bba0b67ed289e07f31a52104c2b2ff9e39c46cda70b83f0b327e8114bcbcdeb4e8f94333ec941f9cd49cfac3af4cad91e247206ce927283482f24d91 languageName: node linkType: hard -"playwright@npm:1.46.0": - version: 1.46.0 - resolution: "playwright@npm:1.46.0" +"playwright@npm:1.46.1": + version: 1.46.1 + resolution: "playwright@npm:1.46.1" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.46.0" + playwright-core: "npm:1.46.1" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10/e06f3b53faaf4edf4fcf636b43004dd0db1e45dbdcb2b59037a9810dfce3a59f0386d4826ba7de42f98fe525539fa20dd8f8c46acd1f8e5c57dcb5c1d8d536ce + checksum: 10/17b0e7495a663dccbda4baf4953823a133af0b7cd4a5978bd2f40768a23e1a92d3659d7b48289a5160c9fa6269d8b9bbf5e2040aa4a63a3dd5f29475343ad3f2 languageName: node linkType: hard From cab5818bc74840818803f9779ae7e6887b56ca42 Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Tue, 20 Aug 2024 12:32:16 -0400 Subject: [PATCH 173/229] Scopes: Add groups property to ScopeDashboardBinding (#92077) - Note: The `dashboardTitle` property and this property (`groups`) are likely to move from the `spec` container to the `status` container within the `scopeDashboardBinding` object in the future. --- pkg/apis/scope/v0alpha1/register.go | 2 ++ pkg/apis/scope/v0alpha1/types.go | 9 +++++++- .../scope/v0alpha1/zz_generated.deepcopy.go | 7 +++++- .../scope/v0alpha1/zz_generated.openapi.go | 22 ++++++++++++++++--- ...enerated.openapi_violation_exceptions.list | 1 + .../example-scope-dashboard-binding-abc.yaml | 4 +++- .../example-scope-dashboard-binding-xyz.yaml | 4 +++- 7 files changed, 42 insertions(+), 7 deletions(-) diff --git a/pkg/apis/scope/v0alpha1/register.go b/pkg/apis/scope/v0alpha1/register.go index 7d301cba8a9..660af02ec4a 100644 --- a/pkg/apis/scope/v0alpha1/register.go +++ b/pkg/apis/scope/v0alpha1/register.go @@ -53,6 +53,7 @@ var ScopeDashboardBindingResourceInfo = common.NewResourceInfo(GROUP, VERSION, {Name: "Created At", Type: "date"}, {Name: "Dashboard", Type: "string"}, {Name: "Scope", Type: "string"}, + {Name: "Groups", Type: "array"}, }, Reader: func(obj any) ([]interface{}, error) { m, ok := obj.(*ScopeDashboardBinding) @@ -64,6 +65,7 @@ var ScopeDashboardBindingResourceInfo = common.NewResourceInfo(GROUP, VERSION, m.CreationTimestamp.UTC().Format(time.RFC3339), m.Spec.Dashboard, m.Spec.Scope, + m.Spec.Groups, }, nil }, }, diff --git a/pkg/apis/scope/v0alpha1/types.go b/pkg/apis/scope/v0alpha1/types.go index 2683368d148..9064687ece0 100644 --- a/pkg/apis/scope/v0alpha1/types.go +++ b/pkg/apis/scope/v0alpha1/types.go @@ -60,9 +60,16 @@ type ScopeDashboardBinding struct { } type ScopeDashboardBindingSpec struct { - Dashboard string `json:"dashboard"` + Dashboard string `json:"dashboard"` + + // DashboardTitle should be populated and update from the dashboard DashboardTitle string `json:"dashboardTitle"` + // Groups is used for the grouping of dashboards that are suggested based + // on a scope. The source of truth for this information has not been + // determined yet. + Groups []string `json:"groups,omitempty"` + Scope string `json:"scope"` } diff --git a/pkg/apis/scope/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/scope/v0alpha1/zz_generated.deepcopy.go index dcf457e6cef..a529a79ab15 100644 --- a/pkg/apis/scope/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/scope/v0alpha1/zz_generated.deepcopy.go @@ -108,7 +108,7 @@ func (in *ScopeDashboardBinding) DeepCopyInto(out *ScopeDashboardBinding) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) return } @@ -166,6 +166,11 @@ func (in *ScopeDashboardBindingList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ScopeDashboardBindingSpec) DeepCopyInto(out *ScopeDashboardBindingSpec) { *out = *in + if in.Groups != nil { + in, out := &in.Groups, &out.Groups + *out = make([]string, len(*in)) + copy(*out, *in) + } return } diff --git a/pkg/apis/scope/v0alpha1/zz_generated.openapi.go b/pkg/apis/scope/v0alpha1/zz_generated.openapi.go index 1bcc51f93a4..af5079a01a8 100644 --- a/pkg/apis/scope/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/scope/v0alpha1/zz_generated.openapi.go @@ -265,9 +265,25 @@ func schema_pkg_apis_scope_v0alpha1_ScopeDashboardBindingSpec(ref common.Referen }, "dashboardTitle": { SchemaProps: spec.SchemaProps{ - Default: "", - Type: []string{"string"}, - Format: "", + Description: "DashboardTitle should be populated and update from the dashboard", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "groups": { + SchemaProps: spec.SchemaProps{ + Description: "Groups is used for the grouping of dashboards that are suggested based on a scope. The source of truth for this information has not been determined yet.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, }, }, "scope": { diff --git a/pkg/apis/scope/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/scope/v0alpha1/zz_generated.openapi_violation_exceptions.list index e0fabd106cb..a50285a2a54 100644 --- a/pkg/apis/scope/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/scope/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,2 +1,3 @@ API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,FindScopeDashboardBindingsResults,Items +API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,ScopeDashboardBindingSpec,Groups API rule violation: names_match,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,ScopeNodeSpec,LinkID diff --git a/pkg/tests/apis/scopes/testdata/example-scope-dashboard-binding-abc.yaml b/pkg/tests/apis/scopes/testdata/example-scope-dashboard-binding-abc.yaml index be175b5613b..d60eac1da96 100644 --- a/pkg/tests/apis/scopes/testdata/example-scope-dashboard-binding-abc.yaml +++ b/pkg/tests/apis/scopes/testdata/example-scope-dashboard-binding-abc.yaml @@ -4,4 +4,6 @@ metadata: name: example_abc spec: scope: example - dashboard: abc \ No newline at end of file + dashboard: abc + dashboardTitle: "Example Dashboard ABC" + groups: ["group1", "group2"] \ No newline at end of file diff --git a/pkg/tests/apis/scopes/testdata/example-scope-dashboard-binding-xyz.yaml b/pkg/tests/apis/scopes/testdata/example-scope-dashboard-binding-xyz.yaml index 98cfa8ed93f..1fd6306aa1b 100644 --- a/pkg/tests/apis/scopes/testdata/example-scope-dashboard-binding-xyz.yaml +++ b/pkg/tests/apis/scopes/testdata/example-scope-dashboard-binding-xyz.yaml @@ -4,4 +4,6 @@ metadata: name: example_xyz spec: scope: example - dashboard: xyz \ No newline at end of file + dashboard: xyz + dashboardTitle: "Example Dashboard XYZ" + groups: ["group2", "group3"] \ No newline at end of file From d27c3822f28e5f26199b4817892d6d24a7a26567 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 20 Aug 2024 15:23:01 -0400 Subject: [PATCH 174/229] Alerting: Add Create and Update methods to Template service (#91981) * rename SetTemplate to UpsertTemplate * Introduce Create\Update methods * update api endpoint to use GetTemplate --- pkg/services/ngalert/api/api_provisioning.go | 15 +- pkg/services/ngalert/provisioning/errors.go | 1 + .../ngalert/provisioning/templates.go | 103 ++++- .../ngalert/provisioning/templates_test.go | 401 +++++++++++++++++- .../provisioning/alerting/text_templates.go | 2 +- 5 files changed, 479 insertions(+), 43 deletions(-) diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index 262c8a05753..6471a4a38fa 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -46,7 +46,7 @@ type ContactPointService interface { type TemplateService interface { GetTemplates(ctx context.Context, orgID int64) ([]definitions.NotificationTemplate, error) GetTemplate(ctx context.Context, orgID int64, name string) (definitions.NotificationTemplate, error) - SetTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) + UpsertTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) DeleteTemplate(ctx context.Context, orgID int64, name string, provenance definitions.Provenance, version string) error } @@ -207,17 +207,12 @@ func (srv *ProvisioningSrv) RouteGetTemplates(c *contextmodel.ReqContext) respon return response.JSON(http.StatusOK, templates) } -func (srv *ProvisioningSrv) RouteGetTemplate(c *contextmodel.ReqContext, name string) response.Response { - templates, err := srv.templates.GetTemplates(c.Req.Context(), c.SignedInUser.GetOrgID()) +func (srv *ProvisioningSrv) RouteGetTemplate(c *contextmodel.ReqContext, nameOrUid string) response.Response { + template, err := srv.templates.GetTemplate(c.Req.Context(), c.SignedInUser.GetOrgID(), nameOrUid) if err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "", err) } - for _, tmpl := range templates { - if tmpl.Name == name { - return response.JSON(http.StatusOK, tmpl) - } - } - return response.Err(provisioning.ErrTemplateNotFound) + return response.JSON(http.StatusOK, template) } func (srv *ProvisioningSrv) RoutePutTemplate(c *contextmodel.ReqContext, body definitions.NotificationTemplateContent, name string) response.Response { @@ -227,7 +222,7 @@ func (srv *ProvisioningSrv) RoutePutTemplate(c *contextmodel.ReqContext, body de Provenance: determineProvenance(c), ResourceVersion: body.ResourceVersion, } - modified, err := srv.templates.SetTemplate(c.Req.Context(), c.SignedInUser.GetOrgID(), tmpl) + modified, err := srv.templates.UpsertTemplate(c.Req.Context(), c.SignedInUser.GetOrgID(), tmpl) if err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "", err) } diff --git a/pkg/services/ngalert/provisioning/errors.go b/pkg/services/ngalert/provisioning/errors.go index 5c23280000c..febdbe49923 100644 --- a/pkg/services/ngalert/provisioning/errors.go +++ b/pkg/services/ngalert/provisioning/errors.go @@ -24,6 +24,7 @@ var ( ErrTemplateNotFound = errutil.NotFound("alerting.notifications.templates.notFound") ErrTemplateInvalid = errutil.BadRequest("alerting.notifications.templates.invalidFormat").MustTemplate("Invalid format of the submitted template", errutil.WithPublic("Template is in invalid format. Correct the payload and try again.")) + ErrTemplateExists = errutil.BadRequest("alerting.notifications.templates.nameExists", errutil.WithPublicMessage("Template file with this name already exists. Use a different name or update existing one.")) ErrContactPointReferenced = errutil.Conflict("alerting.notifications.contact-points.referenced", errutil.WithPublicMessage("Contact point is currently referenced by a notification policy.")) ErrContactPointUsedInRule = errutil.Conflict("alerting.notifications.contact-points.used-by-rule", errutil.WithPublicMessage("Contact point is currently used in the notification settings of one or many alert rules.")) diff --git a/pkg/services/ngalert/provisioning/templates.go b/pkg/services/ngalert/provisioning/templates.go index bed740aa763..457c4549869 100644 --- a/pkg/services/ngalert/provisioning/templates.go +++ b/pkg/services/ngalert/provisioning/templates.go @@ -2,6 +2,7 @@ package provisioning import ( "context" + "errors" "fmt" "hash/fnv" "unsafe" @@ -9,6 +10,7 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage" "github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation" ) @@ -89,7 +91,7 @@ func (t *TemplateService) GetTemplate(ctx context.Context, orgID int64, name str return definitions.NotificationTemplate{}, ErrTemplateNotFound.Errorf("") } -func (t *TemplateService) SetTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) { +func (t *TemplateService) UpsertTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) { err := tmpl.Validate() if err != nil { return definitions.NotificationTemplate{}, MakeErrTemplateInvalid(err) @@ -100,32 +102,99 @@ func (t *TemplateService) SetTemplate(ctx context.Context, orgID int64, tmpl def return definitions.NotificationTemplate{}, err } + d, err := t.updateTemplate(ctx, revision, orgID, tmpl) + if err != nil { + if !errors.Is(err, ErrTemplateNotFound) { + return d, err + } + if tmpl.ResourceVersion != "" { // if version is set then it's an update operation. Fail because resource does not exist anymore + return definitions.NotificationTemplate{}, ErrTemplateNotFound.Errorf("") + } + return t.createTemplate(ctx, revision, orgID, tmpl) + } + return d, err +} + +func (t *TemplateService) CreateTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) { + err := tmpl.Validate() + if err != nil { + return definitions.NotificationTemplate{}, MakeErrTemplateInvalid(err) + } + revision, err := t.configStore.Get(ctx, orgID) + if err != nil { + return definitions.NotificationTemplate{}, err + } + return t.createTemplate(ctx, revision, orgID, tmpl) +} + +func (t *TemplateService) createTemplate(ctx context.Context, revision *legacy_storage.ConfigRevision, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) { if revision.Config.TemplateFiles == nil { revision.Config.TemplateFiles = map[string]string{} } - _, ok := revision.Config.TemplateFiles[tmpl.Name] - if ok { - // check that provenance is not changed in an invalid way - storedProvenance, err := t.provenanceStore.GetProvenance(ctx, &tmpl, orgID) - if err != nil { - return definitions.NotificationTemplate{}, err - } - if err := t.validator(storedProvenance, models.Provenance(tmpl.Provenance)); err != nil { - return definitions.NotificationTemplate{}, err - } + _, found := revision.Config.TemplateFiles[tmpl.Name] + if found { + return definitions.NotificationTemplate{}, ErrTemplateExists.Errorf("") } - existing, ok := revision.Config.TemplateFiles[tmpl.Name] - if ok { - err = t.checkOptimisticConcurrency(tmpl.Name, existing, models.Provenance(tmpl.Provenance), tmpl.ResourceVersion, "update") - if err != nil { - return definitions.NotificationTemplate{}, err + revision.Config.TemplateFiles[tmpl.Name] = tmpl.Template + + err := t.xact.InTransaction(ctx, func(ctx context.Context) error { + if err := t.configStore.Save(ctx, revision, orgID); err != nil { + return err } - } else if tmpl.ResourceVersion != "" { // if version is set then it's an update operation. Fail because resource does not exist anymore + return t.provenanceStore.SetProvenance(ctx, &tmpl, orgID, models.Provenance(tmpl.Provenance)) + }) + if err != nil { + return definitions.NotificationTemplate{}, err + } + + return definitions.NotificationTemplate{ + Name: tmpl.Name, + Template: tmpl.Template, + Provenance: tmpl.Provenance, + ResourceVersion: calculateTemplateFingerprint(tmpl.Template), + }, nil +} + +func (t *TemplateService) UpdateTemplate(ctx context.Context, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) { + err := tmpl.Validate() + if err != nil { + return definitions.NotificationTemplate{}, MakeErrTemplateInvalid(err) + } + + revision, err := t.configStore.Get(ctx, orgID) + if err != nil { + return definitions.NotificationTemplate{}, err + } + return t.updateTemplate(ctx, revision, orgID, tmpl) +} + +func (t *TemplateService) updateTemplate(ctx context.Context, revision *legacy_storage.ConfigRevision, orgID int64, tmpl definitions.NotificationTemplate) (definitions.NotificationTemplate, error) { + if revision.Config.TemplateFiles == nil { + revision.Config.TemplateFiles = map[string]string{} + } + + existingName := tmpl.Name + exisitingContent, found := revision.Config.TemplateFiles[existingName] + if !found { return definitions.NotificationTemplate{}, ErrTemplateNotFound.Errorf("") } + // check that provenance is not changed in an invalid way + storedProvenance, err := t.provenanceStore.GetProvenance(ctx, &tmpl, orgID) + if err != nil { + return definitions.NotificationTemplate{}, err + } + if err := t.validator(storedProvenance, models.Provenance(tmpl.Provenance)); err != nil { + return definitions.NotificationTemplate{}, err + } + + err = t.checkOptimisticConcurrency(tmpl.Name, exisitingContent, models.Provenance(tmpl.Provenance), tmpl.ResourceVersion, "update") + if err != nil { + return definitions.NotificationTemplate{}, err + } + revision.Config.TemplateFiles[tmpl.Name] = tmpl.Template err = t.xact.InTransaction(ctx, func(ctx context.Context) error { diff --git a/pkg/services/ngalert/provisioning/templates_test.go b/pkg/services/ngalert/provisioning/templates_test.go index 2921a8b1499..d7e316defcd 100644 --- a/pkg/services/ngalert/provisioning/templates_test.go +++ b/pkg/services/ngalert/provisioning/templates_test.go @@ -200,7 +200,7 @@ func TestGetTemplate(t *testing.T) { }) } -func TestSetTemplate(t *testing.T) { +func TestUpsertTemplate(t *testing.T) { orgID := int64(1) templateName := "template1" currentTemplateContent := "test1" @@ -240,7 +240,7 @@ func TestSetTemplate(t *testing.T) { ResourceVersion: "", } - result, err := sut.SetTemplate(context.Background(), orgID, tmpl) + result, err := sut.UpsertTemplate(context.Background(), orgID, tmpl) require.NoError(t, err) require.Equal(t, definitions.NotificationTemplate{ @@ -281,7 +281,7 @@ func TestSetTemplate(t *testing.T) { ResourceVersion: calculateTemplateFingerprint("test1"), } - result, err := sut.SetTemplate(context.Background(), orgID, tmpl) + result, err := sut.UpsertTemplate(context.Background(), orgID, tmpl) require.NoError(t, err) assert.Equal(t, definitions.NotificationTemplate{ @@ -317,7 +317,7 @@ func TestSetTemplate(t *testing.T) { ResourceVersion: "", } - result, err := sut.SetTemplate(context.Background(), orgID, tmpl) + result, err := sut.UpsertTemplate(context.Background(), orgID, tmpl) require.NoError(t, err) assert.Equal(t, definitions.NotificationTemplate{ @@ -351,7 +351,7 @@ func TestSetTemplate(t *testing.T) { ResourceVersion: calculateTemplateFingerprint(currentTemplateContent), } - result, _ := sut.SetTemplate(context.Background(), orgID, tmpl) + result, _ := sut.UpsertTemplate(context.Background(), orgID, tmpl) expectedContent := fmt.Sprintf("{{ define \"%s\" }}\n content\n{{ end }}", templateName) require.Equal(t, definitions.NotificationTemplate{ @@ -375,7 +375,7 @@ func TestSetTemplate(t *testing.T) { Name: "name", Template: "{{ .NotAField }}", } - _, err := sut.SetTemplate(context.Background(), 1, tmpl) + _, err := sut.UpsertTemplate(context.Background(), 1, tmpl) require.NoError(t, err) }) @@ -388,7 +388,7 @@ func TestSetTemplate(t *testing.T) { Name: "", Template: "", } - _, err := sut.SetTemplate(context.Background(), orgID, tmpl) + _, err := sut.UpsertTemplate(context.Background(), orgID, tmpl) require.ErrorIs(t, err, ErrTemplateInvalid) }) @@ -397,7 +397,7 @@ func TestSetTemplate(t *testing.T) { Name: "", Template: "{{ .MyField }", } - _, err := sut.SetTemplate(context.Background(), orgID, tmpl) + _, err := sut.UpsertTemplate(context.Background(), orgID, tmpl) require.ErrorIs(t, err, ErrTemplateInvalid) }) @@ -426,7 +426,7 @@ func TestSetTemplate(t *testing.T) { } template.Provenance = definitions.Provenance(models.ProvenanceNone) - _, err := sut.SetTemplate(context.Background(), orgID, template) + _, err := sut.UpsertTemplate(context.Background(), orgID, template) require.ErrorIs(t, err, expectedErr) }) @@ -445,7 +445,7 @@ func TestSetTemplate(t *testing.T) { Provenance: definitions.Provenance(models.ProvenanceNone), } - _, err := sut.SetTemplate(context.Background(), orgID, template) + _, err := sut.UpsertTemplate(context.Background(), orgID, template) require.ErrorIs(t, err, ErrVersionConflict) prov.AssertExpectations(t) @@ -462,7 +462,7 @@ func TestSetTemplate(t *testing.T) { ResourceVersion: "version", Provenance: definitions.Provenance(models.ProvenanceNone), } - _, err := sut.SetTemplate(context.Background(), orgID, template) + _, err := sut.UpsertTemplate(context.Background(), orgID, template) require.ErrorIs(t, err, ErrTemplateNotFound) }) t.Run("propagates errors", func(t *testing.T) { @@ -478,7 +478,7 @@ func TestSetTemplate(t *testing.T) { return nil, expectedErr } - _, err := sut.SetTemplate(context.Background(), orgID, tmpl) + _, err := sut.UpsertTemplate(context.Background(), orgID, tmpl) require.ErrorIs(t, err, expectedErr) }) @@ -490,7 +490,7 @@ func TestSetTemplate(t *testing.T) { expectedErr := errors.New("test") prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, expectedErr) - _, err := sut.SetTemplate(context.Background(), orgID, tmpl) + _, err := sut.UpsertTemplate(context.Background(), orgID, tmpl) require.ErrorIs(t, err, expectedErr) @@ -506,7 +506,7 @@ func TestSetTemplate(t *testing.T) { prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) prov.EXPECT().SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(expectedErr) - _, err := sut.SetTemplate(context.Background(), orgID, tmpl) + _, err := sut.UpsertTemplate(context.Background(), orgID, tmpl) require.ErrorIs(t, err, expectedErr) prov.AssertExpectations(t) @@ -524,7 +524,378 @@ func TestSetTemplate(t *testing.T) { prov.EXPECT().SaveSucceeds() prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) - _, err := sut.SetTemplate(context.Background(), 1, tmpl) + _, err := sut.UpsertTemplate(context.Background(), 1, tmpl) + require.ErrorIs(t, err, expectedErr) + }) + }) +} + +func TestCreateTemplate(t *testing.T) { + orgID := int64(1) + amConfigToken := util.GenerateShortUID() + + tmpl := definitions.NotificationTemplate{ + Name: "new-template", + Template: "{{ define \"test\"}} test {{ end }}", + Provenance: definitions.Provenance(models.ProvenanceAPI), + } + + revision := func() *legacy_storage.ConfigRevision { + return &legacy_storage.ConfigRevision{ + Config: &definitions.PostableUserConfig{}, + ConcurrencyToken: amConfigToken, + } + } + + t.Run("adds new template to config file", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + assert.Equal(t, orgID, org) + return revision(), nil + } + store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error { + assertInTransaction(t, ctx) + return nil + } + prov.EXPECT().SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) { + assertInTransaction(t, ctx) + }).Return(nil) + + result, err := sut.CreateTemplate(context.Background(), orgID, tmpl) + + require.NoError(t, err) + require.Equal(t, definitions.NotificationTemplate{ + Name: tmpl.Name, + Template: tmpl.Template, + Provenance: tmpl.Provenance, + ResourceVersion: calculateTemplateFingerprint(tmpl.Template), + }, result) + + require.Len(t, store.Calls, 2) + + require.Equal(t, "Save", store.Calls[1].Method) + saved := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision) + assert.Equal(t, amConfigToken, saved.ConcurrencyToken) + assert.Contains(t, saved.Config.TemplateFiles, tmpl.Name) + assert.Equal(t, tmpl.Template, saved.Config.TemplateFiles[tmpl.Name]) + + prov.AssertCalled(t, "SetProvenance", mock.Anything, mock.MatchedBy(func(t *definitions.NotificationTemplate) bool { + return t.Name == tmpl.Name + }), orgID, models.ProvenanceAPI) + }) + + t.Run("returns ErrTemplateExists if template exists", func(t *testing.T) { + sut, store, _ := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + assert.Equal(t, orgID, org) + return &legacy_storage.ConfigRevision{ + Config: &definitions.PostableUserConfig{ + TemplateFiles: map[string]string{ + tmpl.Name: "test", + }, + }, + ConcurrencyToken: amConfigToken, + }, nil + } + + _, err := sut.CreateTemplate(context.Background(), orgID, tmpl) + + require.ErrorIs(t, err, ErrTemplateExists) + }) + + t.Run("rejects templates that fail validation", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + + t.Run("empty content", func(t *testing.T) { + tmpl := definitions.NotificationTemplate{ + Name: "", + Template: "", + } + _, err := sut.CreateTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, ErrTemplateInvalid) + }) + + t.Run("invalid content", func(t *testing.T) { + tmpl := definitions.NotificationTemplate{ + Name: "", + Template: "{{ .MyField }", + } + _, err := sut.CreateTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, ErrTemplateInvalid) + }) + + require.Empty(t, store.Calls) + prov.AssertExpectations(t) + }) + + t.Run("propagates errors", func(t *testing.T) { + t.Run("when unable to read config", func(t *testing.T) { + sut, store, _ := createTemplateServiceSut() + expectedErr := errors.New("test") + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return nil, expectedErr + } + + _, err := sut.CreateTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, expectedErr) + }) + + t.Run("when provenance fails to save", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + expectedErr := errors.New("test") + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(expectedErr) + + _, err := sut.CreateTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, expectedErr) + + prov.AssertExpectations(t) + }) + + t.Run("when AM config fails to save", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + expectedErr := errors.New("test") + store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error { + return expectedErr + } + prov.EXPECT().SaveSucceeds() + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) + + _, err := sut.CreateTemplate(context.Background(), 1, tmpl) + require.ErrorIs(t, err, expectedErr) + }) + }) +} + +func TestUpdateTemplate(t *testing.T) { + orgID := int64(1) + currentTemplateContent := "test1" + + tmpl := definitions.NotificationTemplate{ + Name: "template1", + Template: "{{ define \"test\"}} test {{ end }}", + Provenance: definitions.Provenance(models.ProvenanceAPI), + ResourceVersion: "", + } + + amConfigToken := util.GenerateShortUID() + revision := func() *legacy_storage.ConfigRevision { + return &legacy_storage.ConfigRevision{ + Config: &definitions.PostableUserConfig{ + TemplateFiles: map[string]string{ + tmpl.Name: currentTemplateContent, + }, + }, + ConcurrencyToken: amConfigToken, + } + } + + t.Run("returns ErrTemplateNotFound if template does not exist", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + assert.Equal(t, orgID, org) + return &legacy_storage.ConfigRevision{ + Config: &definitions.PostableUserConfig{}, + ConcurrencyToken: amConfigToken, + }, nil + } + _, err := sut.UpdateTemplate(context.Background(), orgID, tmpl) + + require.ErrorIs(t, err, ErrTemplateNotFound) + + require.Len(t, store.Calls, 1) + prov.AssertExpectations(t) + }) + + t.Run("updates current template", func(t *testing.T) { + t.Run("when version matches", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) + prov.EXPECT().SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) { + assertInTransaction(t, ctx) + }).Return(nil) + + result, err := sut.UpdateTemplate(context.Background(), orgID, tmpl) + + require.NoError(t, err) + assert.Equal(t, definitions.NotificationTemplate{ + Name: tmpl.Name, + Template: tmpl.Template, + Provenance: tmpl.Provenance, + ResourceVersion: calculateTemplateFingerprint(tmpl.Template), + }, result) + + require.Len(t, store.Calls, 2) + require.Equal(t, "Save", store.Calls[1].Method) + saved := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision) + assert.Equal(t, amConfigToken, saved.ConcurrencyToken) + assert.Contains(t, saved.Config.TemplateFiles, tmpl.Name) + assert.Equal(t, tmpl.Template, saved.Config.TemplateFiles[tmpl.Name]) + + prov.AssertExpectations(t) + }) + t.Run("bypasses optimistic concurrency validation when version is empty", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) + prov.EXPECT().SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) { + assertInTransaction(t, ctx) + }).Return(nil) + + result, err := sut.UpdateTemplate(context.Background(), orgID, tmpl) + + require.NoError(t, err) + assert.Equal(t, definitions.NotificationTemplate{ + Name: tmpl.Name, + Template: tmpl.Template, + Provenance: tmpl.Provenance, + ResourceVersion: calculateTemplateFingerprint(tmpl.Template), + }, result) + + require.Equal(t, "Save", store.Calls[1].Method) + saved := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision) + assert.Equal(t, amConfigToken, saved.ConcurrencyToken) + assert.Contains(t, saved.Config.TemplateFiles, tmpl.Name) + assert.Equal(t, tmpl.Template, saved.Config.TemplateFiles[tmpl.Name]) + }) + }) + + t.Run("rejects templates that fail validation", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + + t.Run("empty content", func(t *testing.T) { + tmpl := definitions.NotificationTemplate{ + Name: "", + Template: "", + } + _, err := sut.UpdateTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, ErrTemplateInvalid) + }) + + t.Run("invalid content", func(t *testing.T) { + tmpl := definitions.NotificationTemplate{ + Name: "", + Template: "{{ .MyField }", + } + _, err := sut.UpdateTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, ErrTemplateInvalid) + }) + + require.Empty(t, store.Calls) + prov.AssertExpectations(t) + }) + + t.Run("rejects existing templates if provenance is not right", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil) + + expectedErr := errors.New("test") + sut.validator = func(from, to models.Provenance) error { + assert.Equal(t, models.ProvenanceAPI, from) + assert.Equal(t, models.ProvenanceNone, to) + return expectedErr + } + + template := definitions.NotificationTemplate{ + Name: "template1", + Template: "asdf-new", + } + template.Provenance = definitions.Provenance(models.ProvenanceNone) + + _, err := sut.UpdateTemplate(context.Background(), orgID, template) + + require.ErrorIs(t, err, expectedErr) + }) + + t.Run("rejects existing templates if version is not right", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) + + template := definitions.NotificationTemplate{ + Name: "template1", + Template: "asdf-new", + ResourceVersion: "bad-version", + Provenance: definitions.Provenance(models.ProvenanceNone), + } + + _, err := sut.UpdateTemplate(context.Background(), orgID, template) + + require.ErrorIs(t, err, ErrVersionConflict) + prov.AssertExpectations(t) + }) + + t.Run("propagates errors", func(t *testing.T) { + t.Run("when unable to read config", func(t *testing.T) { + sut, store, _ := createTemplateServiceSut() + expectedErr := errors.New("test") + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return nil, expectedErr + } + + _, err := sut.UpdateTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, expectedErr) + }) + + t.Run("when reading provenance status fails", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, org int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + expectedErr := errors.New("test") + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, expectedErr) + + _, err := sut.UpdateTemplate(context.Background(), orgID, tmpl) + + require.ErrorIs(t, err, expectedErr) + + prov.AssertExpectations(t) + }) + + t.Run("when provenance fails to save", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + expectedErr := errors.New("test") + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) + prov.EXPECT().SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(expectedErr) + + _, err := sut.UpdateTemplate(context.Background(), orgID, tmpl) + require.ErrorIs(t, err, expectedErr) + + prov.AssertExpectations(t) + }) + + t.Run("when AM config fails to save", func(t *testing.T) { + sut, store, prov := createTemplateServiceSut() + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return revision(), nil + } + expectedErr := errors.New("test") + store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error { + return expectedErr + } + prov.EXPECT().SaveSucceeds() + prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil) + + _, err := sut.UpdateTemplate(context.Background(), 1, tmpl) require.ErrorIs(t, err, expectedErr) }) }) diff --git a/pkg/services/provisioning/alerting/text_templates.go b/pkg/services/provisioning/alerting/text_templates.go index e64ccdd5959..056b41f1b95 100644 --- a/pkg/services/provisioning/alerting/text_templates.go +++ b/pkg/services/provisioning/alerting/text_templates.go @@ -32,7 +32,7 @@ func (c *defaultTextTemplateProvisioner) Provision(ctx context.Context, for _, file := range files { for _, template := range file.Templates { template.Data.Provenance = definitions.Provenance(models.ProvenanceFile) - _, err := c.templateService.SetTemplate(ctx, template.OrgID, template.Data) + _, err := c.templateService.UpsertTemplate(ctx, template.OrgID, template.Data) if err != nil { return err } From 80a69319b0668c18fd4a64cf79d167420ed3b3bd Mon Sep 17 00:00:00 2001 From: Diego Augusto Molina Date: Tue, 20 Aug 2024 18:35:48 -0300 Subject: [PATCH 175/229] WebAssets: improve checks and error messages on dtos (#92093) fix web assets check and improve error message --- pkg/api/webassets/webassets.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/api/webassets/webassets.go b/pkg/api/webassets/webassets.go index 777dede6db1..44447a911ad 100644 --- a/pkg/api/webassets/webassets.go +++ b/pkg/api/webassets/webassets.go @@ -121,17 +121,17 @@ func readWebAssets(r io.Reader) (*dtos.EntryPointAssets, error) { } if entryPoints.App == nil || len(entryPoints.App.Assets.JS) == 0 { - return nil, fmt.Errorf("missing app entry") + return nil, fmt.Errorf("missing app entry, try running `yarn build`") } if entryPoints.Dark == nil || len(entryPoints.Dark.Assets.CSS) == 0 { - return nil, fmt.Errorf("missing dark entry") + return nil, fmt.Errorf("missing dark entry, try running `yarn build`") } if entryPoints.Light == nil || len(entryPoints.Light.Assets.CSS) == 0 { - return nil, fmt.Errorf("missing light entry") + return nil, fmt.Errorf("missing light entry, try running `yarn build`") + } + if entryPoints.Swagger == nil || len(entryPoints.Swagger.Assets.JS) == 0 { + return nil, fmt.Errorf("missing swagger entry, try running `yarn build`") } - // if entryPoints.Swagger == nil || len(entryPoints.Swagger.Assets.JS) == 0 { - // return nil, fmt.Errorf("missing swagger entry") - // } rsp := &dtos.EntryPointAssets{ JSFiles: make([]dtos.EntryPointAsset, 0, len(entryPoints.App.Assets.JS)), From b6540e2a18ea5a551e1e8447aa44781bb724a796 Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Wed, 21 Aug 2024 09:16:47 +0200 Subject: [PATCH 176/229] SSOSettings: Add api:s (#92018) * apis: add sso setting resource * Implement Storage for sso * Rename packages * Merge identity and sso package * Update table format and expose GetNestedBool * Restructure identity api package --- .../apis/common/v0alpha1/unstructured.go | 8 + .../apis/identity/v0alpha1/types.go | 101 -------- .../apis/identity/v0alpha1/doc.go | 0 .../apis/identity/v0alpha1/register.go | 41 +++- pkg/apis/identity/v0alpha1/types_identity.go | 33 +++ .../v0alpha1/types_servier_account.go | 26 +++ pkg/apis/identity/v0alpha1/types_sso.go | 43 ++++ pkg/apis/identity/v0alpha1/types_team.go | 23 ++ pkg/apis/identity/v0alpha1/types_user.go | 27 +++ .../v0alpha1/zz_generated.deepcopy.go | 77 +++++++ .../v0alpha1/zz_generated.defaults.go | 0 .../identity/v0alpha1/zz_generated.openapi.go | 193 +++++++++++++--- ...enerated.openapi_violation_exceptions.list | 6 +- .../apis/identity/legacy_user_teams.go | 87 ------- pkg/registry/apis/identity/register.go | 87 ++++--- .../{legacy_sa.go => serviceaccount/store.go} | 76 +++--- pkg/registry/apis/identity/sso/store.go | 218 ++++++++++++++++++ .../{legacy_teams.go => team/store.go} | 81 ++++--- .../apis/identity/team/user_team_store.go | 88 +++++++ .../{display.go => user/display_store.go} | 41 ++-- .../{legacy_users.go => user/store.go} | 110 ++++----- pkg/registry/apis/wireset.go | 1 + 22 files changed, 935 insertions(+), 432 deletions(-) delete mode 100644 pkg/apimachinery/apis/identity/v0alpha1/types.go rename pkg/{apimachinery => }/apis/identity/v0alpha1/doc.go (100%) rename pkg/{apimachinery => }/apis/identity/v0alpha1/register.go (78%) create mode 100644 pkg/apis/identity/v0alpha1/types_identity.go create mode 100644 pkg/apis/identity/v0alpha1/types_servier_account.go create mode 100644 pkg/apis/identity/v0alpha1/types_sso.go create mode 100644 pkg/apis/identity/v0alpha1/types_team.go create mode 100644 pkg/apis/identity/v0alpha1/types_user.go rename pkg/{apimachinery => }/apis/identity/v0alpha1/zz_generated.deepcopy.go (78%) rename pkg/{apimachinery => }/apis/identity/v0alpha1/zz_generated.defaults.go (100%) rename pkg/{apimachinery => }/apis/identity/v0alpha1/zz_generated.openapi.go (62%) rename pkg/{apimachinery => }/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list (52%) delete mode 100644 pkg/registry/apis/identity/legacy_user_teams.go rename pkg/registry/apis/identity/{legacy_sa.go => serviceaccount/store.go} (52%) create mode 100644 pkg/registry/apis/identity/sso/store.go rename pkg/registry/apis/identity/{legacy_teams.go => team/store.go} (57%) create mode 100644 pkg/registry/apis/identity/team/user_team_store.go rename pkg/registry/apis/identity/{display.go => user/display_store.go} (76%) rename pkg/registry/apis/identity/{legacy_users.go => user/store.go} (54%) diff --git a/pkg/apimachinery/apis/common/v0alpha1/unstructured.go b/pkg/apimachinery/apis/common/v0alpha1/unstructured.go index 3a735623a68..af26531c59a 100644 --- a/pkg/apimachinery/apis/common/v0alpha1/unstructured.go +++ b/pkg/apimachinery/apis/common/v0alpha1/unstructured.go @@ -101,6 +101,14 @@ func (u *Unstructured) GetNestedString(fields ...string) string { return val } +func (u *Unstructured) GetNestedBool(fields ...string) bool { + val, found, err := unstructured.NestedBool(u.Object, fields...) + if !found || err != nil { + return false + } + return val +} + func (u *Unstructured) GetNestedStringSlice(fields ...string) []string { val, found, err := unstructured.NestedStringSlice(u.Object, fields...) if !found || err != nil { diff --git a/pkg/apimachinery/apis/identity/v0alpha1/types.go b/pkg/apimachinery/apis/identity/v0alpha1/types.go deleted file mode 100644 index 05c286d79ac..00000000000 --- a/pkg/apimachinery/apis/identity/v0alpha1/types.go +++ /dev/null @@ -1,101 +0,0 @@ -package v0alpha1 - -import ( - "github.com/grafana/authlib/claims" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type User struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec UserSpec `json:"spec,omitempty"` -} - -type UserSpec struct { - Name string `json:"name,omitempty"` - Login string `json:"login,omitempty"` - Email string `json:"email,omitempty"` - EmailVerified bool `json:"emailVerified,omitempty"` - Disabled bool `json:"disabled,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type UserList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []User `json:"items,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type Team struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec TeamSpec `json:"spec,omitempty"` -} - -type TeamSpec struct { - Title string `json:"name,omitempty"` - Email string `json:"email,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type TeamList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []Team `json:"items,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type ServiceAccount struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec ServiceAccountSpec `json:"spec,omitempty"` -} - -type ServiceAccountSpec struct { - Name string `json:"name,omitempty"` - Email string `json:"email,omitempty"` - EmailVerified bool `json:"emailVerified,omitempty"` - Disabled bool `json:"disabled,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type ServiceAccountList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []ServiceAccount `json:"items,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -type IdentityDisplayResults struct { - metav1.TypeMeta `json:",inline"` - - // Request keys used to lookup the display value - // +listType=set - Keys []string `json:"keys"` - - // Matching items (the caller may need to remap from keys to results) - // +listType=atomic - Display []IdentityDisplay `json:"display"` - - // Input keys that were not useable - // +listType=set - InvalidKeys []string `json:"invalidKeys,omitempty"` -} - -type IdentityDisplay struct { - IdentityType claims.IdentityType `json:"type"` // The namespaced UID, eg `user|api-key|...` - UID string `json:"uid"` // The namespaced UID, eg `xyz` - Display string `json:"display"` - AvatarURL string `json:"avatarURL,omitempty"` - - // Legacy internal ID -- usage of this value should be phased out - InternalID int64 `json:"internalId,omitempty"` -} diff --git a/pkg/apimachinery/apis/identity/v0alpha1/doc.go b/pkg/apis/identity/v0alpha1/doc.go similarity index 100% rename from pkg/apimachinery/apis/identity/v0alpha1/doc.go rename to pkg/apis/identity/v0alpha1/doc.go diff --git a/pkg/apimachinery/apis/identity/v0alpha1/register.go b/pkg/apis/identity/v0alpha1/register.go similarity index 78% rename from pkg/apimachinery/apis/identity/v0alpha1/register.go rename to pkg/apis/identity/v0alpha1/register.go index 3c1ddaeaa1f..48da15e9797 100644 --- a/pkg/apimachinery/apis/identity/v0alpha1/register.go +++ b/pkg/apis/identity/v0alpha1/register.go @@ -95,6 +95,32 @@ var ServiceAccountResourceInfo = common.NewResourceInfo(GROUP, VERSION, }, ) +var SSOSettingResourceInfo = common.NewResourceInfo( + GROUP, VERSION, "ssosettings", "ssosetting", "SSOSetting", + func() runtime.Object { return &SSOSetting{} }, + func() runtime.Object { return &SSOSettingList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Source", Type: "string"}, + {Name: "Enabled", Type: "boolean"}, + {Name: "Created At", Type: "string", Format: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*SSOSetting) + if !ok { + return nil, fmt.Errorf("expected sso setting") + } + return []interface{}{ + m.Name, + m.Spec.Source, + m.Spec.Settings.GetNestedBool("enabled"), + m.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + }, + }, +) + var ( // SchemeGroupVersion is group version used to register these objects SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION} @@ -105,15 +131,10 @@ var ( AddToScheme = localSchemeBuilder.AddToScheme ) -func init() { - localSchemeBuilder.Register(func(s *runtime.Scheme) error { - return AddKnownTypes(s, VERSION) - }) -} - // Adds the list of known types to the given scheme. -func AddKnownTypes(scheme *runtime.Scheme, version string) error { - scheme.AddKnownTypes(schema.GroupVersion{Group: GROUP, Version: version}, +func AddKnownTypes(scheme *runtime.Scheme, version string) { + scheme.AddKnownTypes( + schema.GroupVersion{Group: GROUP, Version: version}, &User{}, &UserList{}, &ServiceAccount{}, @@ -121,9 +142,9 @@ func AddKnownTypes(scheme *runtime.Scheme, version string) error { &Team{}, &TeamList{}, &IdentityDisplayResults{}, + &SSOSetting{}, + &SSOSettingList{}, ) - // metav1.AddToGroupVersion(scheme, SchemeGroupVersion) - return nil } // Resource takes an unqualified resource and returns a Group qualified GroupResource diff --git a/pkg/apis/identity/v0alpha1/types_identity.go b/pkg/apis/identity/v0alpha1/types_identity.go new file mode 100644 index 00000000000..3ad562f2315 --- /dev/null +++ b/pkg/apis/identity/v0alpha1/types_identity.go @@ -0,0 +1,33 @@ +package v0alpha1 + +import ( + "github.com/grafana/authlib/claims" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type IdentityDisplayResults struct { + metav1.TypeMeta `json:",inline"` + + // Request keys used to lookup the display value + // +listType=set + Keys []string `json:"keys"` + + // Matching items (the caller may need to remap from keys to results) + // +listType=atomic + Display []IdentityDisplay `json:"display"` + + // Input keys that were not useable + // +listType=set + InvalidKeys []string `json:"invalidKeys,omitempty"` +} + +type IdentityDisplay struct { + IdentityType claims.IdentityType `json:"type"` // The namespaced UID, eg `user|api-key|...` + UID string `json:"uid"` // The namespaced UID, eg `xyz` + Display string `json:"display"` + AvatarURL string `json:"avatarURL,omitempty"` + + // Legacy internal ID -- usage of this value should be phased out + InternalID int64 `json:"internalId,omitempty"` +} diff --git a/pkg/apis/identity/v0alpha1/types_servier_account.go b/pkg/apis/identity/v0alpha1/types_servier_account.go new file mode 100644 index 00000000000..a00d8bb7663 --- /dev/null +++ b/pkg/apis/identity/v0alpha1/types_servier_account.go @@ -0,0 +1,26 @@ +package v0alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ServiceAccount struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ServiceAccountSpec `json:"spec,omitempty"` +} + +type ServiceAccountSpec struct { + Name string `json:"name,omitempty"` + Email string `json:"email,omitempty"` + EmailVerified bool `json:"emailVerified,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ServiceAccountList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []ServiceAccount `json:"items,omitempty"` +} diff --git a/pkg/apis/identity/v0alpha1/types_sso.go b/pkg/apis/identity/v0alpha1/types_sso.go new file mode 100644 index 00000000000..64eaed7d4b6 --- /dev/null +++ b/pkg/apis/identity/v0alpha1/types_sso.go @@ -0,0 +1,43 @@ +package v0alpha1 + +import ( + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type SSOSetting struct { + metav1.TypeMeta `json:",inline"` + // Standard object's metadata + // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata + // +optional + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec SSOSettingSpec `json:"spec,omitempty"` +} + +// SSOSettingSpec defines model for SSOSettingSpec. +type SSOSettingSpec struct { + Source Source `json:"source"` + Settings common.Unstructured `json:"settings"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type SSOSettingList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty"` + + Items []SSOSetting `json:"items,omitempty"` +} + +// Source for settings. +// +enum +type Source string + +// Defines values for ItemType. +const ( + SourceDB Source = "db" + // system is from config file, env or argument + SourceSystem Source = "system" +) diff --git a/pkg/apis/identity/v0alpha1/types_team.go b/pkg/apis/identity/v0alpha1/types_team.go new file mode 100644 index 00000000000..7b4ecba072c --- /dev/null +++ b/pkg/apis/identity/v0alpha1/types_team.go @@ -0,0 +1,23 @@ +package v0alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type Team struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TeamSpec `json:"spec,omitempty"` +} +type TeamSpec struct { + Title string `json:"name,omitempty"` + Email string `json:"email,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type TeamList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []Team `json:"items,omitempty"` +} diff --git a/pkg/apis/identity/v0alpha1/types_user.go b/pkg/apis/identity/v0alpha1/types_user.go new file mode 100644 index 00000000000..4fc1f3448b7 --- /dev/null +++ b/pkg/apis/identity/v0alpha1/types_user.go @@ -0,0 +1,27 @@ +package v0alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type User struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec UserSpec `json:"spec,omitempty"` +} + +type UserSpec struct { + Name string `json:"name,omitempty"` + Login string `json:"login,omitempty"` + Email string `json:"email,omitempty"` + EmailVerified bool `json:"emailVerified,omitempty"` + Disabled bool `json:"disabled,omitempty"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type UserList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []User `json:"items,omitempty"` +} diff --git a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/identity/v0alpha1/zz_generated.deepcopy.go similarity index 78% rename from pkg/apimachinery/apis/identity/v0alpha1/zz_generated.deepcopy.go rename to pkg/apis/identity/v0alpha1/zz_generated.deepcopy.go index fba461df2e9..652138bcec2 100644 --- a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/identity/v0alpha1/zz_generated.deepcopy.go @@ -67,6 +67,83 @@ func (in *IdentityDisplayResults) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SSOSetting) DeepCopyInto(out *SSOSetting) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSOSetting. +func (in *SSOSetting) DeepCopy() *SSOSetting { + if in == nil { + return nil + } + out := new(SSOSetting) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SSOSetting) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SSOSettingList) DeepCopyInto(out *SSOSettingList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]SSOSetting, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSOSettingList. +func (in *SSOSettingList) DeepCopy() *SSOSettingList { + if in == nil { + return nil + } + out := new(SSOSettingList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *SSOSettingList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SSOSettingSpec) DeepCopyInto(out *SSOSettingSpec) { + *out = *in + in.Settings.DeepCopyInto(&out.Settings) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SSOSettingSpec. +func (in *SSOSettingSpec) DeepCopy() *SSOSettingSpec { + if in == nil { + return nil + } + out := new(SSOSettingSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) { *out = *in diff --git a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.defaults.go b/pkg/apis/identity/v0alpha1/zz_generated.defaults.go similarity index 100% rename from pkg/apimachinery/apis/identity/v0alpha1/zz_generated.defaults.go rename to pkg/apis/identity/v0alpha1/zz_generated.defaults.go diff --git a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi.go b/pkg/apis/identity/v0alpha1/zz_generated.openapi.go similarity index 62% rename from pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi.go rename to pkg/apis/identity/v0alpha1/zz_generated.openapi.go index a5e4db9ca2b..9ba88920d25 100644 --- a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/identity/v0alpha1/zz_generated.openapi.go @@ -14,21 +14,24 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { return map[string]common.OpenAPIDefinition{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplay": schema_apimachinery_apis_identity_v0alpha1_IdentityDisplay(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplayResults": schema_apimachinery_apis_identity_v0alpha1_IdentityDisplayResults(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccount": schema_apimachinery_apis_identity_v0alpha1_ServiceAccount(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountList": schema_apimachinery_apis_identity_v0alpha1_ServiceAccountList(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountSpec": schema_apimachinery_apis_identity_v0alpha1_ServiceAccountSpec(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.Team": schema_apimachinery_apis_identity_v0alpha1_Team(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamList": schema_apimachinery_apis_identity_v0alpha1_TeamList(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamSpec": schema_apimachinery_apis_identity_v0alpha1_TeamSpec(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.User": schema_apimachinery_apis_identity_v0alpha1_User(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserList": schema_apimachinery_apis_identity_v0alpha1_UserList(ref), - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserSpec": schema_apimachinery_apis_identity_v0alpha1_UserSpec(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.IdentityDisplay": schema_pkg_apis_identity_v0alpha1_IdentityDisplay(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.IdentityDisplayResults": schema_pkg_apis_identity_v0alpha1_IdentityDisplayResults(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSetting": schema_pkg_apis_identity_v0alpha1_SSOSetting(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSettingList": schema_pkg_apis_identity_v0alpha1_SSOSettingList(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSettingSpec": schema_pkg_apis_identity_v0alpha1_SSOSettingSpec(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccount": schema_pkg_apis_identity_v0alpha1_ServiceAccount(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccountList": schema_pkg_apis_identity_v0alpha1_ServiceAccountList(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccountSpec": schema_pkg_apis_identity_v0alpha1_ServiceAccountSpec(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.Team": schema_pkg_apis_identity_v0alpha1_Team(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.TeamList": schema_pkg_apis_identity_v0alpha1_TeamList(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.TeamSpec": schema_pkg_apis_identity_v0alpha1_TeamSpec(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.User": schema_pkg_apis_identity_v0alpha1_User(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.UserList": schema_pkg_apis_identity_v0alpha1_UserList(ref), + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.UserSpec": schema_pkg_apis_identity_v0alpha1_UserSpec(ref), } } -func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplay(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_IdentityDisplay(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -77,7 +80,7 @@ func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplay(ref common.Refer } } -func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplayResults(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_IdentityDisplayResults(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -130,7 +133,7 @@ func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplayResults(ref commo Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplay"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.IdentityDisplay"), }, }, }, @@ -161,11 +164,129 @@ func schema_apimachinery_apis_identity_v0alpha1_IdentityDisplayResults(ref commo }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.IdentityDisplay"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.IdentityDisplay"}, } } -func schema_apimachinery_apis_identity_v0alpha1_ServiceAccount(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_SSOSetting(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSettingSpec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSettingSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_identity_v0alpha1_SSOSettingList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSetting"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.SSOSetting", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_identity_v0alpha1_SSOSettingSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "SSOSettingSpec defines model for SSOSettingSpec.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "source": { + SchemaProps: spec.SchemaProps{ + Description: "Possible enum values:\n - `\"db\"`\n - `\"system\"` system is from config file, env or argument", + Default: "", + Type: []string{"string"}, + Format: "", + Enum: []interface{}{"db", "system"}, + }, + }, + "settings": { + SchemaProps: spec.SchemaProps{ + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"), + }, + }, + }, + Required: []string{"source", "settings"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"}, + } +} + +func schema_pkg_apis_identity_v0alpha1_ServiceAccount(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -194,18 +315,18 @@ func schema_apimachinery_apis_identity_v0alpha1_ServiceAccount(ref common.Refere "spec": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountSpec"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccountSpec"), }, }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccountSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccountSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_ServiceAccountList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -238,7 +359,7 @@ func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountList(ref common.Re Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccount"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccount"), }, }, }, @@ -248,11 +369,11 @@ func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountList(ref common.Re }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.ServiceAccount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.ServiceAccount", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_ServiceAccountSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -288,7 +409,7 @@ func schema_apimachinery_apis_identity_v0alpha1_ServiceAccountSpec(ref common.Re } } -func schema_apimachinery_apis_identity_v0alpha1_Team(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_Team(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -317,18 +438,18 @@ func schema_apimachinery_apis_identity_v0alpha1_Team(ref common.ReferenceCallbac "spec": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamSpec"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.TeamSpec"), }, }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.TeamSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.TeamSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_TeamList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_TeamList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -361,7 +482,7 @@ func schema_apimachinery_apis_identity_v0alpha1_TeamList(ref common.ReferenceCal Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.Team"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.Team"), }, }, }, @@ -371,11 +492,11 @@ func schema_apimachinery_apis_identity_v0alpha1_TeamList(ref common.ReferenceCal }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.Team", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.Team", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_TeamSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_TeamSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -399,7 +520,7 @@ func schema_apimachinery_apis_identity_v0alpha1_TeamSpec(ref common.ReferenceCal } } -func schema_apimachinery_apis_identity_v0alpha1_User(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_User(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -428,18 +549,18 @@ func schema_apimachinery_apis_identity_v0alpha1_User(ref common.ReferenceCallbac "spec": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserSpec"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.UserSpec"), }, }, }, }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.UserSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.UserSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_UserList(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_UserList(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ @@ -472,7 +593,7 @@ func schema_apimachinery_apis_identity_v0alpha1_UserList(ref common.ReferenceCal Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.User"), + Ref: ref("github.com/grafana/grafana/pkg/apis/identity/v0alpha1.User"), }, }, }, @@ -482,11 +603,11 @@ func schema_apimachinery_apis_identity_v0alpha1_UserList(ref common.ReferenceCal }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1.User", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + "github.com/grafana/grafana/pkg/apis/identity/v0alpha1.User", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, } } -func schema_apimachinery_apis_identity_v0alpha1_UserSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { +func schema_pkg_apis_identity_v0alpha1_UserSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ SchemaProps: spec.SchemaProps{ diff --git a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list similarity index 52% rename from pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list rename to pkg/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list index 93ccd0928ad..90ecfe61684 100644 --- a/pkg/apimachinery/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/identity/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,3 +1,3 @@ -API rule violation: names_match,github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1,IdentityDisplay,IdentityType -API rule violation: names_match,github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1,IdentityDisplay,InternalID -API rule violation: names_match,github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1,TeamSpec,Title +API rule violation: names_match,github.com/grafana/grafana/pkg/apis/identity/v0alpha1,IdentityDisplay,IdentityType +API rule violation: names_match,github.com/grafana/grafana/pkg/apis/identity/v0alpha1,IdentityDisplay,InternalID +API rule violation: names_match,github.com/grafana/grafana/pkg/apis/identity/v0alpha1,TeamSpec,Title diff --git a/pkg/registry/apis/identity/legacy_user_teams.go b/pkg/registry/apis/identity/legacy_user_teams.go deleted file mode 100644 index c164ba4dbce..00000000000 --- a/pkg/registry/apis/identity/legacy_user_teams.go +++ /dev/null @@ -1,87 +0,0 @@ -package identity - -import ( - "context" - "net/http" - - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apiserver/pkg/registry/rest" -) - -type userTeamsREST struct { - logger log.Logger - store legacy.LegacyIdentityStore -} - -var ( - _ rest.Storage = (*userTeamsREST)(nil) - _ rest.SingularNameProvider = (*userTeamsREST)(nil) - _ rest.Connecter = (*userTeamsREST)(nil) - _ rest.Scoper = (*userTeamsREST)(nil) - _ rest.StorageMetadata = (*userTeamsREST)(nil) -) - -func newUserTeamsREST(store legacy.LegacyIdentityStore) *userTeamsREST { - return &userTeamsREST{ - logger: log.New("user teams"), - store: store, - } -} - -func (r *userTeamsREST) New() runtime.Object { - return &identity.TeamList{} -} - -func (r *userTeamsREST) Destroy() {} - -func (r *userTeamsREST) NamespaceScoped() bool { - return true -} - -func (r *userTeamsREST) GetSingularName() string { - return "TeamList" // Used for the -} - -func (r *userTeamsREST) ProducesMIMETypes(verb string) []string { - return []string{"application/json"} // and parquet! -} - -func (r *userTeamsREST) ProducesObject(verb string) interface{} { - return &identity.TeamList{} -} - -func (r *userTeamsREST) ConnectMethods() []string { - return []string{"GET"} -} - -func (r *userTeamsREST) NewConnectOptions() (runtime.Object, bool, string) { - return nil, false, "" // true means you can use the trailing path as a variable -} - -func (r *userTeamsREST) Connect(ctx context.Context, name string, _ runtime.Object, responder rest.Responder) (http.Handler, error) { - ns, err := request.NamespaceInfoFrom(ctx, true) - if err != nil { - return nil, err - } - teams, err := r.store.GetUserTeams(ctx, ns, name) - if err != nil { - return nil, err - } - - return http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { - list := &identity.TeamList{} - for _, team := range teams { - t, err := asTeam(&team, ns.Value) - if err != nil { - responder.Error(err) - return - } - list.Items = append(list.Items, *t) - } - responder.Object(200, list) - }), nil -} diff --git a/pkg/registry/apis/identity/register.go b/pkg/registry/apis/identity/register.go index 778602f35a7..a39e59fae19 100644 --- a/pkg/registry/apis/identity/register.go +++ b/pkg/registry/apis/identity/register.go @@ -13,13 +13,18 @@ import ( genericapiserver "k8s.io/apiserver/pkg/server" common "k8s.io/kube-openapi/pkg/common" - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" - identityapi "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/identity" + identityv0 "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" + "github.com/grafana/grafana/pkg/registry/apis/identity/serviceaccount" + "github.com/grafana/grafana/pkg/registry/apis/identity/sso" + "github.com/grafana/grafana/pkg/registry/apis/identity/team" + "github.com/grafana/grafana/pkg/registry/apis/identity/user" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/ssosettings" "github.com/grafana/grafana/pkg/storage/legacysql" ) @@ -27,14 +32,14 @@ var _ builder.APIGroupBuilder = (*IdentityAPIBuilder)(nil) // This is used just so wire has something unique to return type IdentityAPIBuilder struct { - Store legacy.LegacyIdentityStore + Store legacy.LegacyIdentityStore + SSOService ssosettings.Service } func RegisterAPIService( features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar, - // svcTeam team.Service, - // svcUser user.Service, + ssoService ssosettings.Service, sql db.DB, ) (*IdentityAPIBuilder, error) { if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) { @@ -42,34 +47,28 @@ func RegisterAPIService( } builder := &IdentityAPIBuilder{ - Store: legacy.NewLegacySQLStores(legacysql.NewDatabaseProvider(sql)), + Store: legacy.NewLegacySQLStores(legacysql.NewDatabaseProvider(sql)), + SSOService: ssoService, } apiregistration.RegisterAPI(builder) + return builder, nil } func (b *IdentityAPIBuilder) GetGroupVersion() schema.GroupVersion { - return identity.SchemeGroupVersion + return identityv0.SchemeGroupVersion } func (b *IdentityAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { - if err := identity.AddKnownTypes(scheme, identity.VERSION); err != nil { - return err - } + identityv0.AddKnownTypes(scheme, identityv0.VERSION) // Link this version to the internal representation. // This is used for server-side-apply (PATCH), and avoids the error: - // "no kind is registered for the type" - if err := identity.AddKnownTypes(scheme, runtime.APIVersionInternal); err != nil { - return err - } + // "no kind is registered for the type" + identityv0.AddKnownTypes(scheme, runtime.APIVersionInternal) - // If multiple versions exist, then register conversions from zz_generated.conversion.go - // if err := playlist.RegisterConversions(scheme); err != nil { - // return err - // } - metav1.AddToGroupVersion(scheme, identity.SchemeGroupVersion) - return scheme.SetVersionPriority(identity.SchemeGroupVersion) + metav1.AddToGroupVersion(scheme, identityv0.SchemeGroupVersion) + return scheme.SetVersionPriority(identityv0.SchemeGroupVersion) } func (b *IdentityAPIBuilder) GetAPIGroupInfo( @@ -78,53 +77,45 @@ func (b *IdentityAPIBuilder) GetAPIGroupInfo( optsGetter generic.RESTOptionsGetter, dualWriteBuilder grafanarest.DualWriteBuilder, ) (*genericapiserver.APIGroupInfo, error) { - apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(identity.GROUP, scheme, metav1.ParameterCodec, codecs) + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(identityv0.GROUP, scheme, metav1.ParameterCodec, codecs) storage := map[string]rest.Storage{} - team := identity.TeamResourceInfo - teamStore := &legacyTeamStorage{ - service: b.Store, - resourceInfo: team, - tableConverter: team.TableConverter(), - } - storage[team.StoragePath()] = teamStore + teamResource := identityv0.TeamResourceInfo + storage[teamResource.StoragePath()] = team.NewLegacyStore(b.Store) - user := identity.UserResourceInfo - userStore := &legacyUserStorage{ - service: b.Store, - resourceInfo: user, - tableConverter: user.TableConverter(), - } - storage[user.StoragePath()] = userStore - storage[user.StoragePath("teams")] = newUserTeamsREST(b.Store) - - sa := identity.ServiceAccountResourceInfo - saStore := &legacyServiceAccountStorage{ - service: b.Store, - resourceInfo: sa, - tableConverter: sa.TableConverter(), + userResource := identityv0.UserResourceInfo + storage[userResource.StoragePath()] = user.NewLegacyStore(b.Store) + storage[userResource.StoragePath("teams")] = team.NewLegacyUserTeamsStore(b.Store) + + serviceaccountResource := identityv0.ServiceAccountResourceInfo + storage[serviceaccountResource.StoragePath()] = serviceaccount.NewLegacyStore(b.Store) + + if b.SSOService != nil { + ssoResource := identityv0.SSOSettingResourceInfo + storage[ssoResource.StoragePath()] = sso.NewLegacyStore(b.SSOService) } - storage[sa.StoragePath()] = saStore // The display endpoint -- NOTE, this uses a rewrite hack to allow requests without a name parameter - storage["display"] = newDisplayREST(b.Store) + storage["display"] = user.NewLegacyDisplayStore(b.Store) - apiGroupInfo.VersionedResourcesStorageMap[identity.VERSION] = storage + apiGroupInfo.VersionedResourcesStorageMap[identityv0.VERSION] = storage return &apiGroupInfo, nil } func (b *IdentityAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { - return identity.GetOpenAPIDefinitions + return identityv0.GetOpenAPIDefinitions } func (b *IdentityAPIBuilder) GetAPIRoutes() *builder.APIRoutes { - return nil // no custom API routes + // no custom API routes + return nil } func (b *IdentityAPIBuilder) GetAuthorizer() authorizer.Authorizer { + // TODO: handle authorization based in entity. return authorizer.AuthorizerFunc( func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { - user, err := identityapi.GetRequester(ctx) + user, err := identity.GetRequester(ctx) if err != nil { return authorizer.DecisionDeny, "no identity found", err } diff --git a/pkg/registry/apis/identity/legacy_sa.go b/pkg/registry/apis/identity/serviceaccount/store.go similarity index 52% rename from pkg/registry/apis/identity/legacy_sa.go rename to pkg/registry/apis/identity/serviceaccount/store.go index 606b3a6b6a5..2d963c17248 100644 --- a/pkg/registry/apis/identity/legacy_sa.go +++ b/pkg/registry/apis/identity/serviceaccount/store.go @@ -1,59 +1,63 @@ -package identity +package serviceaccount import ( "context" "fmt" "strconv" - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" - "github.com/grafana/grafana/pkg/apimachinery/utils" - "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/user" "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/grafana/pkg/apimachinery/utils" + identityv0 "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/user" ) var ( - _ rest.Scoper = (*legacyServiceAccountStorage)(nil) - _ rest.SingularNameProvider = (*legacyServiceAccountStorage)(nil) - _ rest.Getter = (*legacyServiceAccountStorage)(nil) - _ rest.Lister = (*legacyServiceAccountStorage)(nil) - _ rest.Storage = (*legacyServiceAccountStorage)(nil) + _ rest.Scoper = (*LegacyStore)(nil) + _ rest.SingularNameProvider = (*LegacyStore)(nil) + _ rest.Getter = (*LegacyStore)(nil) + _ rest.Lister = (*LegacyStore)(nil) + _ rest.Storage = (*LegacyStore)(nil) ) -type legacyServiceAccountStorage struct { - service legacy.LegacyIdentityStore - tableConverter rest.TableConvertor - resourceInfo common.ResourceInfo +var resource = identityv0.ServiceAccountResourceInfo + +func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore { + return &LegacyStore{store} +} + +type LegacyStore struct { + store legacy.LegacyIdentityStore } -func (s *legacyServiceAccountStorage) New() runtime.Object { - return s.resourceInfo.NewFunc() +func (s *LegacyStore) New() runtime.Object { + return resource.NewFunc() } -func (s *legacyServiceAccountStorage) Destroy() {} +func (s *LegacyStore) Destroy() {} -func (s *legacyServiceAccountStorage) NamespaceScoped() bool { +func (s *LegacyStore) NamespaceScoped() bool { return true // namespace == org } -func (s *legacyServiceAccountStorage) GetSingularName() string { - return s.resourceInfo.GetSingularName() +func (s *LegacyStore) GetSingularName() string { + return resource.GetSingularName() } -func (s *legacyServiceAccountStorage) NewList() runtime.Object { - return s.resourceInfo.NewListFunc() +func (s *LegacyStore) NewList() runtime.Object { + return resource.NewListFunc() } -func (s *legacyServiceAccountStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) +func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return resource.TableConverter().ConvertToTable(ctx, object, tableOptions) } -func (s *legacyServiceAccountStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { +func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -70,12 +74,12 @@ func (s *legacyServiceAccountStorage) List(ctx context.Context, options *interna } } - found, err := s.service.ListUsers(ctx, ns, query) + found, err := s.store.ListUsers(ctx, ns, query) if err != nil { return nil, err } - list := &identity.ServiceAccountList{} + list := &identityv0.ServiceAccountList{} for _, item := range found.Users { list.Items = append(list.Items, *toSAItem(&item, ns.Value)) } @@ -88,15 +92,15 @@ func (s *legacyServiceAccountStorage) List(ctx context.Context, options *interna return list, err } -func toSAItem(u *user.User, ns string) *identity.ServiceAccount { - item := &identity.ServiceAccount{ +func toSAItem(u *user.User, ns string) *identityv0.ServiceAccount { + item := &identityv0.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ Name: u.UID, Namespace: ns, ResourceVersion: fmt.Sprintf("%d", u.Updated.UnixMilli()), CreationTimestamp: metav1.NewTime(u.Created), }, - Spec: identity.ServiceAccountSpec{ + Spec: identityv0.ServiceAccountSpec{ Name: u.Name, Email: u.Email, EmailVerified: u.EmailVerified, @@ -112,7 +116,7 @@ func toSAItem(u *user.User, ns string) *identity.ServiceAccount { return item } -func (s *legacyServiceAccountStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { +func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -123,12 +127,12 @@ func (s *legacyServiceAccountStorage) Get(ctx context.Context, name string, opti IsServiceAccount: true, } - found, err := s.service.ListUsers(ctx, ns, query) + found, err := s.store.ListUsers(ctx, ns, query) if found == nil || err != nil { - return nil, s.resourceInfo.NewNotFound(name) + return nil, resource.NewNotFound(name) } if len(found.Users) < 1 { - return nil, s.resourceInfo.NewNotFound(name) + return nil, resource.NewNotFound(name) } return toSAItem(&found.Users[0], ns.Value), nil } diff --git a/pkg/registry/apis/identity/sso/store.go b/pkg/registry/apis/identity/sso/store.go new file mode 100644 index 00000000000..0e1fdf5f277 --- /dev/null +++ b/pkg/registry/apis/identity/sso/store.go @@ -0,0 +1,218 @@ +package sso + +import ( + "context" + "errors" + "fmt" + + commonv1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/identity" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/internalversion" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apiserver/pkg/registry/rest" + + identityv0 "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/ssosettings" + ssomodels "github.com/grafana/grafana/pkg/services/ssosettings/models" +) + +var ( + _ rest.Storage = (*LegacyStore)(nil) + _ rest.Scoper = (*LegacyStore)(nil) + _ rest.Getter = (*LegacyStore)(nil) + _ rest.Lister = (*LegacyStore)(nil) + _ rest.Updater = (*LegacyStore)(nil) + _ rest.SingularNameProvider = (*LegacyStore)(nil) + _ rest.GracefulDeleter = (*LegacyStore)(nil) +) + +var resource = identityv0.SSOSettingResourceInfo + +func NewLegacyStore(service ssosettings.Service) *LegacyStore { + return &LegacyStore{service} +} + +type LegacyStore struct { + service ssosettings.Service +} + +// Destroy implements rest.Storage. +func (s *LegacyStore) Destroy() {} + +// NamespaceScoped implements rest.Scoper. +func (s *LegacyStore) NamespaceScoped() bool { + // this is maybe incorrect + return true +} + +// GetSingularName implements rest.SingularNameProvider. +func (s *LegacyStore) GetSingularName() string { + return resource.GetSingularName() +} + +// New implements rest.Storage. +func (s *LegacyStore) New() runtime.Object { + return resource.NewFunc() +} + +// ConvertToTable implements rest.Lister. +func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return resource.TableConverter().ConvertToTable(ctx, object, tableOptions) +} + +// NewList implements rest.Lister. +func (s *LegacyStore) NewList() runtime.Object { + return resource.NewListFunc() +} + +// List implements rest.Lister. +func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + ns, _ := request.NamespaceInfoFrom(ctx, false) + + settings, err := s.service.List(ctx) + if err != nil { + return nil, fmt.Errorf("failed to list sso settings: %w", err) + } + + list := &identityv0.SSOSettingList{} + for _, s := range settings { + list.Items = append(list.Items, mapToObject(ns.Value, s)) + } + + return list, nil +} + +// Get implements rest.Getter. +func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + ns, _ := request.NamespaceInfoFrom(ctx, false) + + setting, err := s.service.GetForProviderWithRedactedSecrets(ctx, name) + if err != nil { + if errors.Is(err, ssosettings.ErrNotFound) { + return nil, resource.NewNotFound(name) + } + return nil, err + } + + object := mapToObject(ns.Value, setting) + return &object, nil +} + +// Update implements rest.Updater. +func (s *LegacyStore) Update( + ctx context.Context, + name string, + objInfo rest.UpdatedObjectInfo, + _ rest.ValidateObjectFunc, + _ rest.ValidateObjectUpdateFunc, + _ bool, + _ *metav1.UpdateOptions, +) (runtime.Object, bool, error) { + const created = false + ident, err := identity.GetRequester(ctx) + if err != nil { + return nil, created, err + } + + old, err := s.Get(ctx, name, nil) + if err != nil { + return old, created, err + } + + obj, err := objInfo.UpdatedObject(ctx, old) + if err != nil { + return old, created, err + } + + setting, ok := obj.(*identityv0.SSOSetting) + if !ok { + return old, created, errors.New("expected ssosetting after update") + } + + if err := s.service.Upsert(ctx, mapToModel(setting), ident); err != nil { + return old, created, err + } + + updated, err := s.Get(ctx, name, nil) + return updated, created, err +} + +// Delete implements rest.GracefulDeleter. +func (s *LegacyStore) Delete( + ctx context.Context, + name string, + _ rest.ValidateObjectFunc, + options *metav1.DeleteOptions, +) (runtime.Object, bool, error) { + obj, err := s.Get(ctx, name, nil) + if err != nil { + return obj, false, err + } + + old, ok := obj.(*identityv0.SSOSetting) + if !ok { + return obj, false, errors.New("expected ssosetting") + } + + // FIXME(kalleep): this should probably be validated in transaction + if options.Preconditions != nil && options.Preconditions.ResourceVersion != nil { + if *options.Preconditions.ResourceVersion != old.GetResourceVersion() { + return old, false, apierrors.NewConflict( + resource.GroupResource(), + name, + fmt.Errorf( + "the ResourceVersion in the precondition (%s) does not match the ResourceVersion in record (%s). The object might have been modified", + *options.Preconditions.ResourceVersion, + old.GetResourceVersion(), + ), + ) + } + } + + if err := s.service.Delete(ctx, name); err != nil { + return old, false, err + } + + // If settings for a provider is deleted from db they will fallback to settings from config file, env or arguments. + afterDelete, err := s.Get(ctx, name, nil) + return afterDelete, false, err +} + +func mapToObject(ns string, s *ssomodels.SSOSettings) identityv0.SSOSetting { + source := identityv0.SourceDB + if s.Source == ssomodels.System { + source = identityv0.SourceSystem + } + + version := "0" + if !s.Updated.IsZero() { + version = fmt.Sprintf("%d", s.Updated.UnixMilli()) + } + + object := identityv0.SSOSetting{ + ObjectMeta: metav1.ObjectMeta{ + Name: s.Provider, + Namespace: ns, + UID: types.UID(s.Provider), + ResourceVersion: version, + CreationTimestamp: metav1.NewTime(s.Updated), + }, + Spec: identityv0.SSOSettingSpec{ + Source: source, + Settings: commonv1.Unstructured{Object: s.Settings}, + }, + } + + return object +} + +func mapToModel(obj *identityv0.SSOSetting) *ssomodels.SSOSettings { + return &ssomodels.SSOSettings{ + Provider: obj.Name, + Settings: obj.Spec.Settings.Object, + } +} diff --git a/pkg/registry/apis/identity/legacy_teams.go b/pkg/registry/apis/identity/team/store.go similarity index 57% rename from pkg/registry/apis/identity/legacy_teams.go rename to pkg/registry/apis/identity/team/store.go index bdcb84550fd..7bfde79ad35 100644 --- a/pkg/registry/apis/identity/legacy_teams.go +++ b/pkg/registry/apis/identity/team/store.go @@ -1,81 +1,86 @@ -package identity +package team import ( "context" "strconv" + "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/authlib/claims" - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" "github.com/grafana/grafana/pkg/apimachinery/utils" + identityv0 "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/team" - "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" ) var ( - _ rest.Scoper = (*legacyTeamStorage)(nil) - _ rest.SingularNameProvider = (*legacyTeamStorage)(nil) - _ rest.Getter = (*legacyTeamStorage)(nil) - _ rest.Lister = (*legacyTeamStorage)(nil) - _ rest.Storage = (*legacyTeamStorage)(nil) + _ rest.Scoper = (*LegacyStore)(nil) + _ rest.SingularNameProvider = (*LegacyStore)(nil) + _ rest.Getter = (*LegacyStore)(nil) + _ rest.Lister = (*LegacyStore)(nil) + _ rest.Storage = (*LegacyStore)(nil) ) -type legacyTeamStorage struct { - service legacy.LegacyIdentityStore - tableConverter rest.TableConvertor - resourceInfo common.ResourceInfo +var resource = identityv0.TeamResourceInfo + +func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore { + return &LegacyStore{store} +} + +type LegacyStore struct { + store legacy.LegacyIdentityStore } -func (s *legacyTeamStorage) New() runtime.Object { - return s.resourceInfo.NewFunc() +func (s *LegacyStore) New() runtime.Object { + return resource.NewFunc() } -func (s *legacyTeamStorage) Destroy() {} +func (s *LegacyStore) Destroy() {} -func (s *legacyTeamStorage) NamespaceScoped() bool { - return true // namespace == org +func (s *LegacyStore) NamespaceScoped() bool { + // namespace == org + return true } -func (s *legacyTeamStorage) GetSingularName() string { - return s.resourceInfo.GetSingularName() +func (s *LegacyStore) GetSingularName() string { + return resource.GetSingularName() } -func (s *legacyTeamStorage) NewList() runtime.Object { - return s.resourceInfo.NewListFunc() +func (s *LegacyStore) NewList() runtime.Object { + return resource.NewListFunc() } -func (s *legacyTeamStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) +func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return resource.TableConverter().ConvertToTable(ctx, object, tableOptions) } -func (s *legacyTeamStorage) doList(ctx context.Context, ns claims.NamespaceInfo, query legacy.ListTeamQuery) (*identity.TeamList, error) { +func (s *LegacyStore) doList(ctx context.Context, ns claims.NamespaceInfo, query legacy.ListTeamQuery) (*identityv0.TeamList, error) { if query.Limit < 1 { query.Limit = 100 } - rsp, err := s.service.ListTeams(ctx, ns, query) + rsp, err := s.store.ListTeams(ctx, ns, query) if err != nil { return nil, err } - list := &identity.TeamList{ + list := &identityv0.TeamList{ ListMeta: metav1.ListMeta{ ResourceVersion: strconv.FormatInt(rsp.RV, 10), }, } for _, team := range rsp.Teams { - item := identity.Team{ + item := identityv0.Team{ ObjectMeta: metav1.ObjectMeta{ Name: team.UID, Namespace: ns.Value, CreationTimestamp: metav1.NewTime(team.Created), ResourceVersion: strconv.FormatInt(team.Updated.UnixMilli(), 10), }, - Spec: identity.TeamSpec{ + Spec: identityv0.TeamSpec{ Title: team.Name, Email: team.Email, }, @@ -97,7 +102,7 @@ func (s *legacyTeamStorage) doList(ctx context.Context, ns claims.NamespaceInfo, return list, nil } -func (s *legacyTeamStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { +func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -115,7 +120,7 @@ func (s *legacyTeamStorage) List(ctx context.Context, options *internalversion.L return s.doList(ctx, ns, query) } -func (s *legacyTeamStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { +func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -131,18 +136,18 @@ func (s *legacyTeamStorage) Get(ctx context.Context, name string, options *metav if len(rsp.Items) > 0 { return &rsp.Items[0], nil } - return nil, s.resourceInfo.NewNotFound(name) + return nil, resource.NewNotFound(name) } -func asTeam(team *team.Team, ns string) (*identity.Team, error) { - item := &identity.Team{ +func asTeam(team *team.Team, ns string) (*identityv0.Team, error) { + item := &identityv0.Team{ ObjectMeta: metav1.ObjectMeta{ Name: team.UID, Namespace: ns, CreationTimestamp: metav1.NewTime(team.Created), ResourceVersion: strconv.FormatInt(team.Updated.UnixMilli(), 10), }, - Spec: identity.TeamSpec{ + Spec: identityv0.TeamSpec{ Title: team.Name, Email: team.Email, }, diff --git a/pkg/registry/apis/identity/team/user_team_store.go b/pkg/registry/apis/identity/team/user_team_store.go new file mode 100644 index 00000000000..2d9128a5e33 --- /dev/null +++ b/pkg/registry/apis/identity/team/user_team_store.go @@ -0,0 +1,88 @@ +package team + +import ( + "context" + "net/http" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/rest" + + identityv0 "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" +) + +var ( + _ rest.Storage = (*LegacyUserTeamsStore)(nil) + _ rest.SingularNameProvider = (*LegacyUserTeamsStore)(nil) + _ rest.Connecter = (*LegacyUserTeamsStore)(nil) + _ rest.Scoper = (*LegacyUserTeamsStore)(nil) + _ rest.StorageMetadata = (*LegacyUserTeamsStore)(nil) +) + +func NewLegacyUserTeamsStore(store legacy.LegacyIdentityStore) *LegacyUserTeamsStore { + return &LegacyUserTeamsStore{ + logger: log.New("user teams"), + store: store, + } +} + +type LegacyUserTeamsStore struct { + logger log.Logger + store legacy.LegacyIdentityStore +} + +func (r *LegacyUserTeamsStore) New() runtime.Object { + return &identityv0.TeamList{} +} + +func (r *LegacyUserTeamsStore) Destroy() {} + +func (r *LegacyUserTeamsStore) NamespaceScoped() bool { + return true +} + +func (r *LegacyUserTeamsStore) GetSingularName() string { + return "TeamList" +} + +func (r *LegacyUserTeamsStore) ProducesMIMETypes(verb string) []string { + return []string{"application/json"} +} + +func (r *LegacyUserTeamsStore) ProducesObject(verb string) interface{} { + return &identityv0.TeamList{} +} + +func (r *LegacyUserTeamsStore) ConnectMethods() []string { + return []string{"GET"} +} + +func (r *LegacyUserTeamsStore) NewConnectOptions() (runtime.Object, bool, string) { + return nil, false, "" // true means you can use the trailing path as a variable +} + +func (r *LegacyUserTeamsStore) Connect(ctx context.Context, name string, _ runtime.Object, responder rest.Responder) (http.Handler, error) { + ns, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, err + } + teams, err := r.store.GetUserTeams(ctx, ns, name) + if err != nil { + return nil, err + } + + return http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) { + list := &identityv0.TeamList{} + for _, team := range teams { + t, err := asTeam(&team, ns.Value) + if err != nil { + responder.Error(err) + return + } + list.Items = append(list.Items, *t) + } + responder.Object(200, list) + }), nil +} diff --git a/pkg/registry/apis/identity/display.go b/pkg/registry/apis/identity/user/display_store.go similarity index 76% rename from pkg/registry/apis/identity/display.go rename to pkg/registry/apis/identity/user/display_store.go index 16527fbdaa2..6d399968520 100644 --- a/pkg/registry/apis/identity/display.go +++ b/pkg/registry/apis/identity/user/display_store.go @@ -1,4 +1,4 @@ -package identity +package user import ( "context" @@ -8,7 +8,7 @@ import ( "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/api/dtos" - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" + identity "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/setting" @@ -18,56 +18,57 @@ import ( "k8s.io/apiserver/pkg/registry/rest" ) -type displayREST struct { +type LegacyDisplayStore struct { store legacy.LegacyIdentityStore } var ( - _ rest.Storage = (*displayREST)(nil) - _ rest.SingularNameProvider = (*displayREST)(nil) - _ rest.Connecter = (*displayREST)(nil) - _ rest.Scoper = (*displayREST)(nil) - _ rest.StorageMetadata = (*displayREST)(nil) + _ rest.Storage = (*LegacyDisplayStore)(nil) + _ rest.SingularNameProvider = (*LegacyDisplayStore)(nil) + _ rest.Connecter = (*LegacyDisplayStore)(nil) + _ rest.Scoper = (*LegacyDisplayStore)(nil) + _ rest.StorageMetadata = (*LegacyDisplayStore)(nil) ) -func newDisplayREST(store legacy.LegacyIdentityStore) *displayREST { - return &displayREST{store} +func NewLegacyDisplayStore(store legacy.LegacyIdentityStore) *LegacyDisplayStore { + return &LegacyDisplayStore{store} } -func (r *displayREST) New() runtime.Object { +func (r *LegacyDisplayStore) New() runtime.Object { return &identity.IdentityDisplayResults{} } -func (r *displayREST) Destroy() {} +func (r *LegacyDisplayStore) Destroy() {} -func (r *displayREST) NamespaceScoped() bool { +func (r *LegacyDisplayStore) NamespaceScoped() bool { return true } -func (r *displayREST) GetSingularName() string { - return "IdentityDisplay" // not actually used anywhere, but required by SingularNameProvider +func (r *LegacyDisplayStore) GetSingularName() string { + // not actually used anywhere, but required by SingularNameProvider + return "IdentityDisplay" } -func (r *displayREST) ProducesMIMETypes(verb string) []string { +func (r *LegacyDisplayStore) ProducesMIMETypes(verb string) []string { return []string{"application/json"} } -func (r *displayREST) ProducesObject(verb string) any { +func (r *LegacyDisplayStore) ProducesObject(verb string) any { return &identity.IdentityDisplayResults{} } -func (r *displayREST) ConnectMethods() []string { +func (r *LegacyDisplayStore) ConnectMethods() []string { return []string{"GET"} } -func (r *displayREST) NewConnectOptions() (runtime.Object, bool, string) { +func (r *LegacyDisplayStore) NewConnectOptions() (runtime.Object, bool, string) { return nil, false, "" // true means you can use the trailing path as a variable } // This will always have an empty app url var fakeCfgForGravatar = &setting.Cfg{} -func (r *displayREST) Connect(ctx context.Context, name string, _ runtime.Object, responder rest.Responder) (http.Handler, error) { +func (r *LegacyDisplayStore) Connect(ctx context.Context, name string, _ runtime.Object, responder rest.Responder) (http.Handler, error) { // See: /pkg/services/apiserver/builder/helper.go#L34 // The name is set with a rewriter hack if name != "name" { diff --git a/pkg/registry/apis/identity/legacy_users.go b/pkg/registry/apis/identity/user/store.go similarity index 54% rename from pkg/registry/apis/identity/legacy_users.go rename to pkg/registry/apis/identity/user/store.go index 321e755c8fa..80066c20efc 100644 --- a/pkg/registry/apis/identity/legacy_users.go +++ b/pkg/registry/apis/identity/user/store.go @@ -1,59 +1,63 @@ -package identity +package user import ( "context" "fmt" "strconv" - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" - identity "github.com/grafana/grafana/pkg/apimachinery/apis/identity/v0alpha1" - "github.com/grafana/grafana/pkg/apimachinery/utils" - "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/user" "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/grafana/pkg/apimachinery/utils" + identityv0 "github.com/grafana/grafana/pkg/apis/identity/v0alpha1" + "github.com/grafana/grafana/pkg/registry/apis/identity/legacy" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/user" ) var ( - _ rest.Scoper = (*legacyUserStorage)(nil) - _ rest.SingularNameProvider = (*legacyUserStorage)(nil) - _ rest.Getter = (*legacyUserStorage)(nil) - _ rest.Lister = (*legacyUserStorage)(nil) - _ rest.Storage = (*legacyUserStorage)(nil) + _ rest.Scoper = (*LegacyStore)(nil) + _ rest.SingularNameProvider = (*LegacyStore)(nil) + _ rest.Getter = (*LegacyStore)(nil) + _ rest.Lister = (*LegacyStore)(nil) + _ rest.Storage = (*LegacyStore)(nil) ) -type legacyUserStorage struct { - service legacy.LegacyIdentityStore - tableConverter rest.TableConvertor - resourceInfo common.ResourceInfo +var resource = identityv0.UserResourceInfo + +func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore { + return &LegacyStore{store} +} + +type LegacyStore struct { + store legacy.LegacyIdentityStore } -func (s *legacyUserStorage) New() runtime.Object { - return s.resourceInfo.NewFunc() +func (s *LegacyStore) New() runtime.Object { + return resource.NewFunc() } -func (s *legacyUserStorage) Destroy() {} +func (s *LegacyStore) Destroy() {} -func (s *legacyUserStorage) NamespaceScoped() bool { +func (s *LegacyStore) NamespaceScoped() bool { return true // namespace == org } -func (s *legacyUserStorage) GetSingularName() string { - return s.resourceInfo.GetSingularName() +func (s *LegacyStore) GetSingularName() string { + return resource.GetSingularName() } -func (s *legacyUserStorage) NewList() runtime.Object { - return s.resourceInfo.NewListFunc() +func (s *LegacyStore) NewList() runtime.Object { + return resource.NewListFunc() } -func (s *legacyUserStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { - return s.tableConverter.ConvertToTable(ctx, object, tableOptions) +func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + return resource.TableConverter().ConvertToTable(ctx, object, tableOptions) } -func (s *legacyUserStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { +func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err @@ -70,12 +74,12 @@ func (s *legacyUserStorage) List(ctx context.Context, options *internalversion.L } } - found, err := s.service.ListUsers(ctx, ns, query) + found, err := s.store.ListUsers(ctx, ns, query) if err != nil { return nil, err } - list := &identity.UserList{} + list := &identityv0.UserList{} for _, item := range found.Users { list.Items = append(list.Items, *toUserItem(&item, ns.Value)) } @@ -88,15 +92,36 @@ func (s *legacyUserStorage) List(ctx context.Context, options *internalversion.L return list, err } -func toUserItem(u *user.User, ns string) *identity.User { - item := &identity.User{ +func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + ns, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, err + } + query := legacy.ListUserQuery{ + OrgID: ns.OrgID, + Limit: 1, + IsServiceAccount: false, + } + + found, err := s.store.ListUsers(ctx, ns, query) + if found == nil || err != nil { + return nil, resource.NewNotFound(name) + } + if len(found.Users) < 1 { + return nil, resource.NewNotFound(name) + } + return toUserItem(&found.Users[0], ns.Value), nil +} + +func toUserItem(u *user.User, ns string) *identityv0.User { + item := &identityv0.User{ ObjectMeta: metav1.ObjectMeta{ Name: u.UID, Namespace: ns, ResourceVersion: fmt.Sprintf("%d", u.Updated.UnixMilli()), CreationTimestamp: metav1.NewTime(u.Created), }, - Spec: identity.UserSpec{ + Spec: identityv0.UserSpec{ Name: u.Name, Login: u.Login, Email: u.Email, @@ -112,24 +137,3 @@ func toUserItem(u *user.User, ns string) *identity.User { }) return item } - -func (s *legacyUserStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { - ns, err := request.NamespaceInfoFrom(ctx, true) - if err != nil { - return nil, err - } - query := legacy.ListUserQuery{ - OrgID: ns.OrgID, - Limit: 1, - IsServiceAccount: false, - } - - found, err := s.service.ListUsers(ctx, ns, query) - if found == nil || err != nil { - return nil, s.resourceInfo.NewNotFound(name) - } - if len(found.Users) < 1 { - return nil, s.resourceInfo.NewNotFound(name) - } - return toUserItem(&found.Users[0], ns.Value), nil -} diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index f7eb37f3064..f6ceafac35e 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -39,4 +39,5 @@ var WireSet = wire.NewSet( query.RegisterAPIService, scope.RegisterAPIService, notifications.RegisterAPIService, + //sso.RegisterAPIService, ) From 0c55d0aa64090b0a45852cca8c454a0c1b1aeb0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ida=20=C5=A0tambuk?= Date: Wed, 21 Aug 2024 09:21:57 +0100 Subject: [PATCH 177/229] Features: Move openSearchBackendFlowEnabled toggle to GA (#92117) --- .../configure-grafana/feature-toggles/index.md | 2 +- pkg/services/featuremgmt/registry.go | 3 ++- pkg/services/featuremgmt/toggles_gen.csv | 2 +- pkg/services/featuremgmt/toggles_gen.json | 12 ++++++++---- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index d76bb3845ec..3dcb474dd6c 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -67,6 +67,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes | | `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes | | `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | | +| `openSearchBackendFlowEnabled` | Enables the backend query flow for Open Search datasource plugin | Yes | | `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes | ## Public preview feature toggles @@ -103,7 +104,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `ssoSettingsSAML` | Use the new SSO Settings API to configure the SAML connector | | `accessActionSets` | Introduces action sets for resource permissions. Also ensures that all folder editors and admins can create subfolders without needing any additional permissions. | | `azureMonitorPrometheusExemplars` | Allows configuration of Azure Monitor as a data source that can provide Prometheus exemplars | -| `openSearchBackendFlowEnabled` | Enables the backend query flow for Open Search datasource plugin | | `cloudwatchMetricInsightsCrossAccount` | Enables cross account observability for Cloudwatch Metric Insights query builder | ## Experimental feature toggles diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index e2a563a3c97..db598c1d193 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1297,8 +1297,9 @@ var ( { Name: "openSearchBackendFlowEnabled", Description: "Enables the backend query flow for Open Search datasource plugin", - Stage: FeatureStagePublicPreview, + Stage: FeatureStageGeneralAvailability, Owner: awsDatasourcesSquad, + Expression: "true", }, { Name: "ssoSettingsLDAP", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 6ce7ef77d4a..c9315a75b12 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -170,7 +170,7 @@ pluginProxyPreserveTrailingSlash,GA,@grafana/plugins-platform-backend,false,fals azureMonitorPrometheusExemplars,preview,@grafana/partner-datasources,false,false,false pinNavItems,experimental,@grafana/grafana-frontend-platform,false,false,false authZGRPCServer,experimental,@grafana/identity-access-team,false,false,false -openSearchBackendFlowEnabled,preview,@grafana/aws-datasources,false,false,false +openSearchBackendFlowEnabled,GA,@grafana/aws-datasources,false,false,false ssoSettingsLDAP,experimental,@grafana/identity-access-team,false,false,false failWrongDSUID,experimental,@grafana/plugins-platform-backend,false,false,false databaseReadReplica,experimental,@grafana/grafana-backend-services-squad,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index a545cc71a6e..40b7aad2501 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1898,13 +1898,17 @@ { "metadata": { "name": "openSearchBackendFlowEnabled", - "resourceVersion": "1718727528075", - "creationTimestamp": "2024-06-17T09:41:50Z" + "resourceVersion": "1724141158995", + "creationTimestamp": "2024-06-17T09:41:50Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-08-20 08:05:58.995762 +0000 UTC" + } }, "spec": { "description": "Enables the backend query flow for Open Search datasource plugin", - "stage": "preview", - "codeowner": "@grafana/aws-datasources" + "stage": "GA", + "codeowner": "@grafana/aws-datasources", + "expression": "true" } }, { From e7c628f4e7fafc48f840c4923f1e573f3d01d5ee Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:45:37 +0300 Subject: [PATCH 178/229] Bump scenes to 5.10.1 (#92140) * bump scenes to 5.10.1 * mock config version * refactor * refactor --- package.json | 2 +- .../scene/DashboardAnnotationsDataLayer.test.ts | 1 + .../serialization/transformSceneToSaveModel.test.ts | 1 + yarn.lock | 10 +++++----- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3a4086d45c0..a82145f88c0 100644 --- a/package.json +++ b/package.json @@ -266,7 +266,7 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "^5.8.0", + "@grafana/scenes": "^5.10.1", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/features/dashboard-scene/scene/DashboardAnnotationsDataLayer.test.ts b/public/app/features/dashboard-scene/scene/DashboardAnnotationsDataLayer.test.ts index 039f6b8ccef..9fcd33bf1af 100644 --- a/public/app/features/dashboard-scene/scene/DashboardAnnotationsDataLayer.test.ts +++ b/public/app/features/dashboard-scene/scene/DashboardAnnotationsDataLayer.test.ts @@ -33,6 +33,7 @@ jest.mock('@grafana/runtime', () => ({ return runRequestMock(ds, request); }, config: { + ...jest.requireActual('@grafana/runtime').config, publicDashboardAccessToken: 'ac123', }, })); diff --git a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts index e31b146b0de..9b02fcc2f6a 100644 --- a/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts +++ b/public/app/features/dashboard-scene/serialization/transformSceneToSaveModel.test.ts @@ -141,6 +141,7 @@ jest.mock('@grafana/runtime', () => ({ return runRequestMock(ds, request); }, config: { + ...jest.requireActual('@grafana/runtime').config, panels: { text: { skipDataQuery: true }, }, diff --git a/yarn.lock b/yarn.lock index 7bba332c4fb..497c2bdab53 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3739,9 +3739,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:^5.8.0": - version: 5.8.0 - resolution: "@grafana/scenes@npm:5.8.0" +"@grafana/scenes@npm:^5.10.1": + version: 5.10.1 + resolution: "@grafana/scenes@npm:5.10.1" dependencies: "@grafana/e2e-selectors": "npm:^11.0.0" "@leeoniya/ufuzzy": "npm:^1.0.14" @@ -3756,7 +3756,7 @@ __metadata: "@grafana/ui": ">=10.4" react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/1c550dd5256371de0849ae64d167c4a9dbd99be0c03f15116ac213d3a9e2ec4b5248331086ca9d6616b5ad8f2be5be503772ac38a853e32da57f485ef3addb41 + checksum: 10/81397b728344952f55f61254b76a840440fcf85614d309c54f13c00c7e5c1eeb49a95a8229e223bb7730f59e1c1cc7cc149bf6ebbb6e9ad7304e49e3daeda524 languageName: node linkType: hard @@ -18163,7 +18163,7 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:^5.8.0" + "@grafana/scenes": "npm:^5.10.1" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0" From aea8b60849aff1fb6a7d2c77cf32496fef119b08 Mon Sep 17 00:00:00 2001 From: Will Browne Date: Wed, 21 Aug 2024 09:46:41 +0100 Subject: [PATCH 179/229] Plugins: Add support for fetching plugin includes from plugin CDN (#91476) * update oss side * add Rel func to plugins.FS * update tests * add comment * fix fs paths for nested plugin * fix test * fix sources * fix cdn class bug * update tests * remove commented out code --- pkg/plugins/ifaces.go | 1 + pkg/plugins/localfiles.go | 4 + pkg/plugins/manager/fakes/fakes.go | 22 +++-- .../manager/loader/assetpath/assetpath.go | 58 +++++++++-- .../loader/assetpath/assetpath_test.go | 98 +++++++++++++++---- .../manager/pipeline/bootstrap/factory.go | 15 +-- .../manager/pipeline/bootstrap/steps_test.go | 8 +- .../manager/signature/manifest_test.go | 4 + pkg/plugins/plugins.go | 1 + pkg/plugins/pluginscdn/pluginscdn.go | 5 + pkg/plugins/test_utils.go | 4 + .../pluginstore/store_test.go | 10 +- 12 files changed, 183 insertions(+), 47 deletions(-) diff --git a/pkg/plugins/ifaces.go b/pkg/plugins/ifaces.go index b8f5d20ed1e..5606d4b6259 100644 --- a/pkg/plugins/ifaces.go +++ b/pkg/plugins/ifaces.go @@ -69,6 +69,7 @@ type FS interface { Base() string Files() ([]string, error) + Rel(string) (string, error) } type FSRemover interface { diff --git a/pkg/plugins/localfiles.go b/pkg/plugins/localfiles.go index 5981d13256e..0385f69dc94 100644 --- a/pkg/plugins/localfiles.go +++ b/pkg/plugins/localfiles.go @@ -77,6 +77,10 @@ func (f LocalFS) fileIsAllowed(basePath string, absolutePath string, info os.Fil return true, nil } +func (f LocalFS) Rel(p string) (string, error) { + return filepath.Rel(f.basePath, p) +} + // walkFunc returns a filepath.WalkFunc that accumulates absolute file paths into acc by walking over f.Base(). // f.fileIsAllowed is used as WalkFunc, see its documentation for more information on which files are collected. func (f LocalFS) walkFunc(basePath string, acc map[string]struct{}) filepath.WalkFunc { diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index b648e94c579..8abcb907b32 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -403,35 +403,43 @@ func (f *FakeActionSetRegistry) RegisterActionSets(_ context.Context, _ string, return f.ExpectedErr } -type FakePluginFiles struct { +type FakePluginFS struct { OpenFunc func(name string) (fs.File, error) RemoveFunc func() error + RelFunc func(string) (string, error) base string } -func NewFakePluginFiles(base string) *FakePluginFiles { - return &FakePluginFiles{ +func NewFakePluginFS(base string) *FakePluginFS { + return &FakePluginFS{ base: base, } } -func (f *FakePluginFiles) Open(name string) (fs.File, error) { +func (f *FakePluginFS) Open(name string) (fs.File, error) { if f.OpenFunc != nil { return f.OpenFunc(name) } return nil, nil } -func (f *FakePluginFiles) Base() string { +func (f *FakePluginFS) Rel(_ string) (string, error) { + if f.RelFunc != nil { + return f.RelFunc(f.base) + } + return "", nil +} + +func (f *FakePluginFS) Base() string { return f.base } -func (f *FakePluginFiles) Files() ([]string, error) { +func (f *FakePluginFS) Files() ([]string, error) { return []string{}, nil } -func (f *FakePluginFiles) Remove() error { +func (f *FakePluginFS) Remove() error { if f.RemoveFunc != nil { return f.RemoveFunc() } diff --git a/pkg/plugins/manager/loader/assetpath/assetpath.go b/pkg/plugins/manager/loader/assetpath/assetpath.go index eb24b0434a8..e26ce3bae1d 100644 --- a/pkg/plugins/manager/loader/assetpath/assetpath.go +++ b/pkg/plugins/manager/loader/assetpath/assetpath.go @@ -27,14 +27,16 @@ func ProvideService(cfg *config.PluginManagementCfg, cdn *pluginscdn.Service) *S type PluginInfo struct { pluginJSON plugins.JSONData class plugins.Class - dir string + fs plugins.FS + parent *PluginInfo } -func NewPluginInfo(pluginJSON plugins.JSONData, class plugins.Class, fs plugins.FS) PluginInfo { +func NewPluginInfo(pluginJSON plugins.JSONData, class plugins.Class, fs plugins.FS, parent *PluginInfo) PluginInfo { return PluginInfo{ pluginJSON: pluginJSON, class: class, - dir: fs.Base(), + fs: fs, + parent: parent, } } @@ -45,33 +47,77 @@ func DefaultService(cfg *config.PluginManagementCfg) *Service { // Base returns the base path for the specified plugin. func (s *Service) Base(n PluginInfo) (string, error) { if n.class == plugins.ClassCore { - baseDir := getBaseDir(n.dir) + baseDir := getBaseDir(n.fs.Base()) return path.Join("public/app/plugins", string(n.pluginJSON.Type), baseDir), nil } + if n.class == plugins.ClassCDN { + return n.fs.Base(), nil + } if s.cdn.PluginSupported(n.pluginJSON.ID) { return s.cdn.AssetURL(n.pluginJSON.ID, n.pluginJSON.Info.Version, "") } + if n.parent != nil { + if s.cdn.PluginSupported(n.parent.pluginJSON.ID) { + relPath, err := n.parent.fs.Rel(n.fs.Base()) + if err != nil { + return "", err + } + return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, relPath) + } + } + return path.Join("public/plugins", n.pluginJSON.ID), nil } // Module returns the module.js path for the specified plugin. func (s *Service) Module(n PluginInfo) (string, error) { if n.class == plugins.ClassCore { - if filepath.Base(n.dir) == "dist" { + if filepath.Base(n.fs.Base()) == "dist" { // The core plugin has been built externally, use the module from the dist folder } else { - baseDir := getBaseDir(n.dir) + baseDir := getBaseDir(n.fs.Base()) return path.Join("core:plugin", baseDir), nil } } + if n.class == plugins.ClassCDN { + return pluginscdn.JoinPath(n.fs.Base(), "module.js") + } + if s.cdn.PluginSupported(n.pluginJSON.ID) { return s.cdn.AssetURL(n.pluginJSON.ID, n.pluginJSON.Info.Version, "module.js") } + if n.parent != nil { + if s.cdn.PluginSupported(n.parent.pluginJSON.ID) { + relPath, err := n.parent.fs.Rel(n.fs.Base()) + if err != nil { + return "", err + } + return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, path.Join(relPath, "module.js")) + } + } + return path.Join("public/plugins", n.pluginJSON.ID, "module.js"), nil } // RelativeURL returns the relative URL for an arbitrary plugin asset. func (s *Service) RelativeURL(n PluginInfo, pathStr string) (string, error) { + if n.class == plugins.ClassCDN { + return pluginscdn.JoinPath(n.fs.Base(), pathStr) + } + + if s.cdn.PluginSupported(n.pluginJSON.ID) { + return s.cdn.NewCDNURLConstructor(n.pluginJSON.ID, n.pluginJSON.Info.Version).StringPath(pathStr) + } + if n.parent != nil { + if s.cdn.PluginSupported(n.parent.pluginJSON.ID) { + relPath, err := n.parent.fs.Rel(n.fs.Base()) + if err != nil { + return "", err + } + return s.cdn.AssetURL(n.parent.pluginJSON.ID, n.parent.pluginJSON.Info.Version, path.Join(relPath, pathStr)) + } + } + if s.cdn.PluginSupported(n.pluginJSON.ID) { return s.cdn.NewCDNURLConstructor(n.pluginJSON.ID, n.pluginJSON.Info.Version).StringPath(pathStr) } diff --git a/pkg/plugins/manager/loader/assetpath/assetpath_test.go b/pkg/plugins/manager/loader/assetpath/assetpath_test.go index 9802dcfe00b..be8ce730b95 100644 --- a/pkg/plugins/manager/loader/assetpath/assetpath_test.go +++ b/pkg/plugins/manager/loader/assetpath/assetpath_test.go @@ -2,6 +2,7 @@ package assetpath import ( "net/url" + "path" "strings" "testing" @@ -13,8 +14,8 @@ import ( "github.com/grafana/grafana/pkg/plugins/pluginscdn" ) -func extPath(pluginID string) *fakes.FakePluginFiles { - return fakes.NewFakePluginFiles(pluginID) +func pluginFS(basePath string) *fakes.FakePluginFS { + return fakes.NewFakePluginFS(basePath) } func TestService(t *testing.T) { @@ -45,7 +46,7 @@ func TestService(t *testing.T) { } svc := ProvideService(cfg, pluginscdn.ProvideService(cfg)) - tableOldFS := fakes.NewFakePluginFiles("/grafana/public/app/plugins/panel/table-old") + tableOldFS := fakes.NewFakePluginFS("/grafana/public/app/plugins/panel/table-old") jsonData := map[string]plugins.JSONData{ "table-old": {ID: "table-old", Info: plugins.Info{Version: "1.0.0"}}, @@ -60,37 +61,75 @@ func TestService(t *testing.T) { }) t.Run("Base", func(t *testing.T) { - base, err := svc.Base(NewPluginInfo(jsonData["one"], plugins.ClassExternal, extPath("one"))) + base, err := svc.Base(NewPluginInfo(jsonData["one"], plugins.ClassExternal, pluginFS("one"), nil)) require.NoError(t, err) - u, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one") + oneCDNURL, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one") require.NoError(t, err) - require.Equal(t, u, base) + require.Equal(t, oneCDNURL, base) - base, err = svc.Base(NewPluginInfo(jsonData["two"], plugins.ClassExternal, extPath("two"))) + base, err = svc.Base(NewPluginInfo(jsonData["one"], plugins.ClassCDN, pluginFS(oneCDNURL), nil)) + require.NoError(t, err) + require.Equal(t, oneCDNURL, base) + + base, err = svc.Base(NewPluginInfo(jsonData["two"], plugins.ClassExternal, pluginFS("two"), nil)) require.NoError(t, err) require.Equal(t, "public/plugins/two", base) - base, err = svc.Base(NewPluginInfo(jsonData["table-old"], plugins.ClassCore, tableOldFS)) + base, err = svc.Base(NewPluginInfo(jsonData["table-old"], plugins.ClassCore, tableOldFS, nil)) require.NoError(t, err) require.Equal(t, "public/app/plugins/table-old", base) + + parentFS := pluginFS(oneCDNURL) + parentFS.RelFunc = func(_ string) (string, error) { + return "child-plugins/two", nil + } + parent := NewPluginInfo(jsonData["one"], plugins.ClassExternal, parentFS, nil) + child := NewPluginInfo(jsonData["two"], plugins.ClassExternal, fakes.NewFakePluginFS(""), &parent) + base, err = svc.Base(child) + require.NoError(t, err) + + childBase, err := url.JoinPath(oneCDNURL, "child-plugins/two") + require.NoError(t, err) + require.Equal(t, childBase, base) }) t.Run("Module", func(t *testing.T) { - module, err := svc.Module(NewPluginInfo(jsonData["one"], plugins.ClassExternal, extPath("one"))) + module, err := svc.Module(NewPluginInfo(jsonData["one"], plugins.ClassExternal, pluginFS("one"), nil)) require.NoError(t, err) - u, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one/module.js") + oneCDNURL, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one") require.NoError(t, err) - require.Equal(t, u, module) - module, err = svc.Module(NewPluginInfo(jsonData["two"], plugins.ClassExternal, extPath("two"))) + oneCDNModuleURL, err := url.JoinPath(oneCDNURL, "module.js") + require.NoError(t, err) + require.Equal(t, oneCDNModuleURL, module) + + fs := pluginFS("one") + module, err = svc.Module(NewPluginInfo(jsonData["one"], plugins.ClassCDN, fs, nil)) + require.NoError(t, err) + require.Equal(t, path.Join(fs.Base(), "module.js"), module) + + module, err = svc.Module(NewPluginInfo(jsonData["two"], plugins.ClassExternal, pluginFS("two"), nil)) require.NoError(t, err) require.Equal(t, "public/plugins/two/module.js", module) - module, err = svc.Module(NewPluginInfo(jsonData["table-old"], plugins.ClassCore, tableOldFS)) + module, err = svc.Module(NewPluginInfo(jsonData["table-old"], plugins.ClassCore, tableOldFS, nil)) require.NoError(t, err) require.Equal(t, "core:plugin/table-old", module) + + parentFS := pluginFS(oneCDNURL) + parentFS.RelFunc = func(_ string) (string, error) { + return "child-plugins/two", nil + } + parent := NewPluginInfo(jsonData["one"], plugins.ClassExternal, parentFS, nil) + child := NewPluginInfo(jsonData["two"], plugins.ClassExternal, fakes.NewFakePluginFS(""), &parent) + module, err = svc.Module(child) + require.NoError(t, err) + + childModule, err := url.JoinPath(oneCDNURL, "child-plugins/two/module.js") + require.NoError(t, err) + require.Equal(t, childModule, module) }) t.Run("RelativeURL", func(t *testing.T) { @@ -103,24 +142,47 @@ func TestService(t *testing.T) { }, } - u, err := svc.RelativeURL(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, extPath("one")), "") + u, err := svc.RelativeURL(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, pluginFS("one"), nil), "") require.NoError(t, err) // given an empty path, base URL will be returned - baseURL, err := svc.Base(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, extPath("one"))) + baseURL, err := svc.Base(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, pluginFS("one"), nil)) require.NoError(t, err) require.Equal(t, baseURL, u) - u, err = svc.RelativeURL(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, extPath("one")), "path/to/file.txt") + u, err = svc.RelativeURL(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, pluginFS("one"), nil), "path/to/file.txt") require.NoError(t, err) require.Equal(t, strings.TrimRight(tc.cdnBaseURL, "/")+"/one/1.0.0/public/plugins/one/path/to/file.txt", u) - u, err = svc.RelativeURL(NewPluginInfo(pluginsMap["two"].JSONData, plugins.ClassExternal, extPath("two")), "path/to/file.txt") + u, err = svc.RelativeURL(NewPluginInfo(pluginsMap["two"].JSONData, plugins.ClassExternal, pluginFS("two"), nil), "path/to/file.txt") require.NoError(t, err) require.Equal(t, "public/plugins/two/path/to/file.txt", u) - u, err = svc.RelativeURL(NewPluginInfo(pluginsMap["two"].JSONData, plugins.ClassExternal, extPath("two")), "default") + u, err = svc.RelativeURL(NewPluginInfo(pluginsMap["two"].JSONData, plugins.ClassExternal, pluginFS("two"), nil), "default") require.NoError(t, err) require.Equal(t, "public/plugins/two/default", u) + + oneCDNURL, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one") + require.NoError(t, err) + + u, err = svc.RelativeURL(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassCDN, pluginFS(oneCDNURL), nil), "path/to/file.txt") + require.NoError(t, err) + + oneCDNRelativeURL, err := url.JoinPath(oneCDNURL, "path/to/file.txt") + require.NoError(t, err) + require.Equal(t, oneCDNRelativeURL, u) + + parentFS := pluginFS(oneCDNURL) + parentFS.RelFunc = func(_ string) (string, error) { + return "child-plugins/two", nil + } + parent := NewPluginInfo(jsonData["one"], plugins.ClassExternal, parentFS, nil) + child := NewPluginInfo(jsonData["two"], plugins.ClassExternal, fakes.NewFakePluginFS(""), &parent) + u, err = svc.RelativeURL(child, "path/to/file.txt") + require.NoError(t, err) + + oneCDNRelativeURL, err = url.JoinPath(oneCDNURL, "child-plugins/two/path/to/file.txt") + require.NoError(t, err) + require.Equal(t, oneCDNRelativeURL, u) }) }) } diff --git a/pkg/plugins/manager/pipeline/bootstrap/factory.go b/pkg/plugins/manager/pipeline/bootstrap/factory.go index c5f23865a67..9b6b5fd718b 100644 --- a/pkg/plugins/manager/pipeline/bootstrap/factory.go +++ b/pkg/plugins/manager/pipeline/bootstrap/factory.go @@ -25,7 +25,8 @@ func NewDefaultPluginFactory(assetPath *assetpath.Service) *DefaultPluginFactory func (f *DefaultPluginFactory) createPlugin(bundle *plugins.FoundBundle, class plugins.Class, sig plugins.Signature) (*plugins.Plugin, error) { - plugin, err := f.newPlugin(bundle.Primary, class, sig) + parentInfo := assetpath.NewPluginInfo(bundle.Primary.JSONData, class, bundle.Primary.FS, nil) + plugin, err := f.newPlugin(bundle.Primary, class, sig, parentInfo) if err != nil { return nil, err } @@ -36,7 +37,8 @@ func (f *DefaultPluginFactory) createPlugin(bundle *plugins.FoundBundle, class p plugin.Children = make([]*plugins.Plugin, 0, len(bundle.Children)) for _, child := range bundle.Children { - cp, err := f.newPlugin(*child, class, sig) + childInfo := assetpath.NewPluginInfo(child.JSONData, class, child.FS, &parentInfo) + cp, err := f.newPlugin(*child, class, sig, childInfo) if err != nil { return nil, err } @@ -47,8 +49,8 @@ func (f *DefaultPluginFactory) createPlugin(bundle *plugins.FoundBundle, class p return plugin, nil } -func (f *DefaultPluginFactory) newPlugin(p plugins.FoundPlugin, class plugins.Class, sig plugins.Signature) (*plugins.Plugin, error) { - info := assetpath.NewPluginInfo(p.JSONData, class, p.FS) +func (f *DefaultPluginFactory) newPlugin(p plugins.FoundPlugin, class plugins.Class, sig plugins.Signature, + info assetpath.PluginInfo) (*plugins.Plugin, error) { baseURL, err := f.assetPath.Base(info) if err != nil { return nil, fmt.Errorf("base url: %w", err) @@ -69,14 +71,13 @@ func (f *DefaultPluginFactory) newPlugin(p plugins.FoundPlugin, class plugins.Cl } plugin.SetLogger(log.New(fmt.Sprintf("plugin.%s", plugin.ID))) - if err = setImages(plugin, f.assetPath); err != nil { + if err = setImages(plugin, f.assetPath, info); err != nil { return nil, err } return plugin, nil } -func setImages(p *plugins.Plugin, assetPath *assetpath.Service) error { - info := assetpath.NewPluginInfo(p.JSONData, p.Class, p.FS) +func setImages(p *plugins.Plugin, assetPath *assetpath.Service, info assetpath.PluginInfo) error { var err error for _, dst := range []*string{&p.Info.Logos.Small, &p.Info.Logos.Large} { if len(*dst) == 0 { diff --git a/pkg/plugins/manager/pipeline/bootstrap/steps_test.go b/pkg/plugins/manager/pipeline/bootstrap/steps_test.go index 31eedd6ddc2..3bb8ca78e02 100644 --- a/pkg/plugins/manager/pipeline/bootstrap/steps_test.go +++ b/pkg/plugins/manager/pipeline/bootstrap/steps_test.go @@ -98,7 +98,7 @@ func TestTemplateDecorateFunc(t *testing.T) { func Test_configureAppChildPlugin(t *testing.T) { t.Run("Child plugin will inherit parent version information when version is empty", func(t *testing.T) { child := &plugins.Plugin{ - FS: fakes.NewFakePluginFiles("c:\\grafana\\public\\app\\plugins\\app\\testdata-app\\datasources\\datasource"), + FS: fakes.NewFakePluginFS("c:\\grafana\\public\\app\\plugins\\app\\testdata-app\\datasources\\datasource"), } parent := &plugins.Plugin{ JSONData: plugins.JSONData{ @@ -107,7 +107,7 @@ func Test_configureAppChildPlugin(t *testing.T) { Info: plugins.Info{Version: "1.0.0"}, }, Class: plugins.ClassCore, - FS: fakes.NewFakePluginFiles("c:\\grafana\\public\\app\\plugins\\app\\testdata-app"), + FS: fakes.NewFakePluginFS("c:\\grafana\\public\\app\\plugins\\app\\testdata-app"), BaseURL: "public/app/plugins/app/testdata-app", } @@ -119,7 +119,7 @@ func Test_configureAppChildPlugin(t *testing.T) { t.Run("Child plugin will not inherit parent version information when version is non-empty", func(t *testing.T) { child := &plugins.Plugin{ - FS: fakes.NewFakePluginFiles("/plugins/parent-app/child-panel"), + FS: fakes.NewFakePluginFS("/plugins/parent-app/child-panel"), JSONData: plugins.JSONData{ Info: plugins.Info{Version: "2.0.2"}, }, @@ -131,7 +131,7 @@ func Test_configureAppChildPlugin(t *testing.T) { Info: plugins.Info{Version: "2.0.0"}, }, Class: plugins.ClassExternal, - FS: fakes.NewFakePluginFiles("/plugins/parent-app"), + FS: fakes.NewFakePluginFS("/plugins/parent-app"), BaseURL: "plugins/parent-app", } diff --git a/pkg/plugins/manager/signature/manifest_test.go b/pkg/plugins/manager/signature/manifest_test.go index 1414f4d61b2..cb7364ed93a 100644 --- a/pkg/plugins/manager/signature/manifest_test.go +++ b/pkg/plugins/manager/signature/manifest_test.go @@ -351,6 +351,10 @@ func (f fsPathSeparatorFiles) Files() ([]string, error) { return files, nil } +func (f fsPathSeparatorFiles) Rel(base string) (string, error) { + return filepath.Rel(f.Base(), strings.ReplaceAll(base, f.separator, string(filepath.Separator))) +} + func (f fsPathSeparatorFiles) Open(name string) (fs.File, error) { return f.FS.Open(strings.ReplaceAll(name, f.separator, string(filepath.Separator))) } diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index 1f2cc1aa513..b52585c46ab 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -505,6 +505,7 @@ const ( ClassCore Class = "core" ClassBundled Class = "bundled" ClassExternal Class = "external" + ClassCDN Class = "cdn" ) func (c Class) String() string { diff --git a/pkg/plugins/pluginscdn/pluginscdn.go b/pkg/plugins/pluginscdn/pluginscdn.go index 44445d449fd..4161954b981 100644 --- a/pkg/plugins/pluginscdn/pluginscdn.go +++ b/pkg/plugins/pluginscdn/pluginscdn.go @@ -2,6 +2,7 @@ package pluginscdn import ( "errors" + "net/url" "strings" "github.com/grafana/grafana/pkg/plugins/config" @@ -62,3 +63,7 @@ func (s *Service) AssetURL(pluginID, pluginVersion, assetPath string) (string, e } return s.NewCDNURLConstructor(pluginID, pluginVersion).StringPath(assetPath) } + +func JoinPath(base string, assetPath ...string) (string, error) { + return url.JoinPath(base, assetPath...) +} diff --git a/pkg/plugins/test_utils.go b/pkg/plugins/test_utils.go index 97f5f3d3d9a..bdf731855ec 100644 --- a/pkg/plugins/test_utils.go +++ b/pkg/plugins/test_utils.go @@ -36,6 +36,10 @@ func (f inMemoryFS) Files() ([]string, error) { return fps, nil } +func (f inMemoryFS) Rel(_ string) (string, error) { + return "", nil +} + func (f inMemoryFS) Open(fn string) (fs.File, error) { if _, ok := f.files[fn]; !ok { return nil, ErrFileNotExist diff --git a/pkg/services/pluginsintegration/pluginstore/store_test.go b/pkg/services/pluginsintegration/pluginstore/store_test.go index f6817b13d00..a4ac6434ff3 100644 --- a/pkg/services/pluginsintegration/pluginstore/store_test.go +++ b/pkg/services/pluginsintegration/pluginstore/store_test.go @@ -118,11 +118,11 @@ func TestStore_Plugins(t *testing.T) { func TestStore_Routes(t *testing.T) { t.Run("Routes returns all static routes for non-decommissioned plugins", func(t *testing.T) { - p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "a-test-renderer", Type: plugins.TypeRenderer}, FS: fakes.NewFakePluginFiles("/some/dir")} - p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "b-test-panel", Type: plugins.TypePanel}, FS: fakes.NewFakePluginFiles("/grafana/")} - p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "c-test-secrets", Type: plugins.TypeSecretsManager}, FS: fakes.NewFakePluginFiles("./secrets"), Class: plugins.ClassCore} - p4 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "d-test-datasource", Type: plugins.TypeDataSource}, FS: fakes.NewFakePluginFiles("../test")} - p5 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "e-test-app", Type: plugins.TypeApp}, FS: fakes.NewFakePluginFiles("any/path")} + p1 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "a-test-renderer", Type: plugins.TypeRenderer}, FS: fakes.NewFakePluginFS("/some/dir")} + p2 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "b-test-panel", Type: plugins.TypePanel}, FS: fakes.NewFakePluginFS("/grafana/")} + p3 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "c-test-secrets", Type: plugins.TypeSecretsManager}, FS: fakes.NewFakePluginFS("./secrets"), Class: plugins.ClassCore} + p4 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "d-test-datasource", Type: plugins.TypeDataSource}, FS: fakes.NewFakePluginFS("../test")} + p5 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "e-test-app", Type: plugins.TypeApp}, FS: fakes.NewFakePluginFS("any/path")} p6 := &plugins.Plugin{JSONData: plugins.JSONData{ID: "f-test-app", Type: plugins.TypeApp}} p6.RegisterClient(&DecommissionedPlugin{}) From a8c6000c19c140d0159ce53aa59cf6a9f2f8d476 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 09:49:14 +0100 Subject: [PATCH 180/229] Update dependency @prometheus-io/lezer-promql to v0.53.2 (#92148) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- packages/grafana-prometheus/package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 8d219537957..69f2aae2001 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -49,7 +49,7 @@ "@lezer/common": "1.2.1", "@lezer/highlight": "1.2.1", "@lezer/lr": "1.4.2", - "@prometheus-io/lezer-promql": "0.53.1", + "@prometheus-io/lezer-promql": "0.53.2", "@reduxjs/toolkit": "2.2.7", "d3": "7.9.0", "date-fns": "3.6.0", diff --git a/yarn.lock b/yarn.lock index 497c2bdab53..a4d0eb127ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3571,7 +3571,7 @@ __metadata: "@lezer/common": "npm:1.2.1" "@lezer/highlight": "npm:1.2.1" "@lezer/lr": "npm:1.4.2" - "@prometheus-io/lezer-promql": "npm:0.53.1" + "@prometheus-io/lezer-promql": "npm:0.53.2" "@reduxjs/toolkit": "npm:2.2.7" "@rollup/plugin-image": "npm:3.0.3" "@rollup/plugin-node-resolve": "npm:15.2.3" @@ -5855,13 +5855,13 @@ __metadata: languageName: node linkType: hard -"@prometheus-io/lezer-promql@npm:0.53.1": - version: 0.53.1 - resolution: "@prometheus-io/lezer-promql@npm:0.53.1" +"@prometheus-io/lezer-promql@npm:0.53.2": + version: 0.53.2 + resolution: "@prometheus-io/lezer-promql@npm:0.53.2" peerDependencies: "@lezer/highlight": ^1.1.2 "@lezer/lr": ^1.2.3 - checksum: 10/ebb506155f6343277e7bafc7342af3b8c0f162eda08c380f680f8c526878211a17be92e75b5d5de69cbf344f50a76221472b50533dde152ea396e148278c2d77 + checksum: 10/dcf094b29cca967a79f8a5f6a9f7006d84efe03465d0eba177bd42cb5d340e4e8d2dbd718e2220fbf19cab5bd831ec4c882b629ad69dd72297fa1d37fb9c18c2 languageName: node linkType: hard From 584559d3c2a0e480a9fa7aef6fae9d12a4078633 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 08:54:57 +0000 Subject: [PATCH 181/229] Update dependency @react-awesome-query-builder/ui to v6.6.3 --- package.json | 2 +- packages/grafana-sql/package.json | 2 +- yarn.lock | 22 +++++++++++----------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index a82145f88c0..26bfa768854 100644 --- a/package.json +++ b/package.json @@ -289,7 +289,7 @@ "@react-aria/focus": "3.18.2", "@react-aria/overlays": "3.23.2", "@react-aria/utils": "3.25.2", - "@react-awesome-query-builder/ui": "6.6.2", + "@react-awesome-query-builder/ui": "6.6.3", "@reduxjs/toolkit": "2.2.7", "@testing-library/react-hooks": "^8.0.1", "@visx/event": "3.3.0", diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 30cf4f8dfef..68ecf2c1cf3 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -20,7 +20,7 @@ "@grafana/experimental": "1.7.13", "@grafana/runtime": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", - "@react-awesome-query-builder/ui": "6.6.2", + "@react-awesome-query-builder/ui": "6.6.3", "immutable": "4.3.7", "lodash": "4.17.21", "react": "18.2.0", diff --git a/yarn.lock b/yarn.lock index a4d0eb127ed..de54cdaa338 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3789,7 +3789,7 @@ __metadata: "@grafana/runtime": "npm:11.3.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@grafana/ui": "npm:11.3.0-pre" - "@react-awesome-query-builder/ui": "npm:6.6.2" + "@react-awesome-query-builder/ui": "npm:6.6.3" "@testing-library/dom": "npm:10.0.0" "@testing-library/jest-dom": "npm:^6.1.2" "@testing-library/react": "npm:15.0.2" @@ -6390,9 +6390,9 @@ __metadata: languageName: node linkType: hard -"@react-awesome-query-builder/core@npm:^6.6.2": - version: 6.6.2 - resolution: "@react-awesome-query-builder/core@npm:6.6.2" +"@react-awesome-query-builder/core@npm:^6.6.3": + version: 6.6.3 + resolution: "@react-awesome-query-builder/core@npm:6.6.3" dependencies: "@babel/runtime": "npm:^7.24.5" clone: "npm:^2.1.2" @@ -6403,15 +6403,15 @@ __metadata: moment: "npm:^2.30.1" spel2js: "npm:^0.2.8" sqlstring: "npm:^2.3.3" - checksum: 10/9b959833923bc736dea491137ee1b04ec0f9432076b1df9ce7a5cf517834bae4a2361fb83241976c56646020c8a9e60b02e91901b43ee313fd7efbafd04f5e73 + checksum: 10/33c8703e4df6b315105c9ec5f8af0430326a82ea3f57544c3baecbe4c6b041fb65c8b785504ee59e3e464a9af1b5e2c9681584d1dd1355a370123ada1df5ec42 languageName: node linkType: hard -"@react-awesome-query-builder/ui@npm:6.6.2": - version: 6.6.2 - resolution: "@react-awesome-query-builder/ui@npm:6.6.2" +"@react-awesome-query-builder/ui@npm:6.6.3": + version: 6.6.3 + resolution: "@react-awesome-query-builder/ui@npm:6.6.3" dependencies: - "@react-awesome-query-builder/core": "npm:^6.6.2" + "@react-awesome-query-builder/core": "npm:^6.6.3" classnames: "npm:^2.5.1" lodash: "npm:^4.17.21" prop-types: "npm:^15.8.1" @@ -6420,7 +6420,7 @@ __metadata: peerDependencies: react: ^16.8.4 || ^17.0.1 || ^18.0.0 react-dom: ^16.8.4 || ^17.0.1 || ^18.0.0 - checksum: 10/29246a9f6ccce54b6caf141bc1fe8856c0de07f4cdfddfb04e17eb544abf280979074145e27bbd7ae4fa874b4d0adad379ecbb0c211101a4ce8a2924bd3c3118 + checksum: 10/095fd1921e384c24e7895405234c1a09c2365002411be745b1af68e857364c74fb0bc82b5a4ce6a222cabeb0079e390bb3cd04cad4a16b0d095998e88020fda4 languageName: node linkType: hard @@ -18190,7 +18190,7 @@ __metadata: "@react-aria/focus": "npm:3.18.2" "@react-aria/overlays": "npm:3.23.2" "@react-aria/utils": "npm:3.25.2" - "@react-awesome-query-builder/ui": "npm:6.6.2" + "@react-awesome-query-builder/ui": "npm:6.6.3" "@react-types/button": "npm:3.9.6" "@react-types/menu": "npm:3.9.11" "@react-types/overlays": "npm:3.8.9" From 292982f30bfb863fcc5c664d2600369b6224b9f7 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 21 Aug 2024 11:01:53 +0100 Subject: [PATCH 182/229] Collapse: stop cropping focus rings (#92190) remove overflow: hidden; from bodyContentWrapper --- packages/grafana-ui/src/components/Collapse/Collapse.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Collapse/Collapse.tsx b/packages/grafana-ui/src/components/Collapse/Collapse.tsx index 97e0389aa8a..f7d67fc8b23 100644 --- a/packages/grafana-ui/src/components/Collapse/Collapse.tsx +++ b/packages/grafana-ui/src/components/Collapse/Collapse.tsx @@ -33,7 +33,6 @@ const getStyles = (theme: GrafanaTheme2) => ({ bodyContentWrapper: css({ label: 'bodyContentWrapper', flex: 1, - overflow: 'hidden', }), loader: css({ label: 'collapse__loader', From ef832219ed73881b3976c8551e529cd536f90161 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 09:36:16 +0000 Subject: [PATCH 183/229] Update dependency @types/ini to v4.1.1 --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index de54cdaa338..4696112cfd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9514,9 +9514,9 @@ __metadata: linkType: hard "@types/ini@npm:^4": - version: 4.1.0 - resolution: "@types/ini@npm:4.1.0" - checksum: 10/43dc756f60a4b2e828371baa0c5db006f3d31a2d58877f88ff15a58815aa804a612eea35adfc2c0e99ba09632b7a96bdf4a55ccaf5f164598f9ee314ad1171a1 + version: 4.1.1 + resolution: "@types/ini@npm:4.1.1" + checksum: 10/5d17a4af098bcf0263c767515a3856ebdd61a84ba78bd132e1cf7d05ed29a928af4ea80657a1226b39a4c9b3d5e1349a2bbaaa0c5c7ec068034ba7ae768f63cf languageName: node linkType: hard From 9494e9b8476afab8522417f31ecceb1ef52cba34 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 10:05:46 +0000 Subject: [PATCH 184/229] Update dependency @types/react-transition-group to v4.4.11 --- package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 26bfa768854..debe0de0e51 100644 --- a/package.json +++ b/package.json @@ -134,7 +134,7 @@ "@types/react-router-dom": "5.3.3", "@types/react-table": "7.7.20", "@types/react-test-renderer": "18.3.0", - "@types/react-transition-group": "4.4.10", + "@types/react-transition-group": "4.4.11", "@types/react-virtualized-auto-sizer": "1.0.4", "@types/react-window": "1.8.8", "@types/react-window-infinite-loader": "^1", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 940b6b5a86c..18e53426fe0 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -153,7 +153,7 @@ "@types/react-highlight-words": "0.20.0", "@types/react-router-dom": "5.3.3", "@types/react-test-renderer": "18.3.0", - "@types/react-transition-group": "4.4.10", + "@types/react-transition-group": "4.4.11", "@types/react-window": "1.8.8", "@types/slate": "0.47.11", "@types/slate-plain-serializer": "0.7.5", diff --git a/yarn.lock b/yarn.lock index 4696112cfd6..aabd114da63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3900,7 +3900,7 @@ __metadata: "@types/react-router-dom": "npm:5.3.3" "@types/react-table": "npm:7.7.20" "@types/react-test-renderer": "npm:18.3.0" - "@types/react-transition-group": "npm:4.4.10" + "@types/react-transition-group": "npm:4.4.11" "@types/react-window": "npm:1.8.8" "@types/slate": "npm:0.47.11" "@types/slate-plain-serializer": "npm:0.7.5" @@ -9956,12 +9956,12 @@ __metadata: languageName: node linkType: hard -"@types/react-transition-group@npm:4.4.10, @types/react-transition-group@npm:^4.4.0": - version: 4.4.10 - resolution: "@types/react-transition-group@npm:4.4.10" +"@types/react-transition-group@npm:4.4.11, @types/react-transition-group@npm:^4.4.0": + version: 4.4.11 + resolution: "@types/react-transition-group@npm:4.4.11" dependencies: "@types/react": "npm:*" - checksum: 10/b429f3bd54d9aea6c0395943ce2dda6b76fb458e902365bd91fd99bf72064fb5d59e2b74e78d10f2871908501d350da63e230d81bda2b616c967cab8dc51bd16 + checksum: 10/a7f4de6e5f57d9fcdea027e22873c633f96a803c96d422db8b99a45c36a9cceb7882d152136bbc31c7158fc1827e37aea5070d369724bb71dd11b5687332bc4d languageName: node linkType: hard @@ -18245,7 +18245,7 @@ __metadata: "@types/react-router-dom": "npm:5.3.3" "@types/react-table": "npm:7.7.20" "@types/react-test-renderer": "npm:18.3.0" - "@types/react-transition-group": "npm:4.4.10" + "@types/react-transition-group": "npm:4.4.11" "@types/react-virtualized-auto-sizer": "npm:1.0.4" "@types/react-window": "npm:1.8.8" "@types/react-window-infinite-loader": "npm:^1" From f26258984ad39837e4919a108f1d0e0099c1f8bf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:02:15 +0000 Subject: [PATCH 185/229] Update dependency core-js to v3.38.1 --- package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index debe0de0e51..30e5b842012 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,7 @@ "chrome-remote-interface": "0.33.2", "codeowners": "^5.1.1", "copy-webpack-plugin": "12.0.2", - "core-js": "3.38.0", + "core-js": "3.38.1", "css-loader": "7.1.2", "css-minimizer-webpack-plugin": "6.0.0", "cypress": "13.10.0", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 18e53426fe0..f70c2dfc234 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -163,7 +163,7 @@ "@types/uuid": "9.0.8", "chance": "1.1.12", "common-tags": "1.8.2", - "core-js": "3.38.0", + "core-js": "3.38.1", "css-loader": "7.1.2", "csstype": "3.1.3", "esbuild": "0.20.2", diff --git a/yarn.lock b/yarn.lock index aabd114da63..b6bb178c41f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3913,7 +3913,7 @@ __metadata: chance: "npm:1.1.12" classnames: "npm:2.5.1" common-tags: "npm:1.8.2" - core-js: "npm:3.38.0" + core-js: "npm:3.38.1" css-loader: "npm:7.1.2" csstype: "npm:3.1.3" d3: "npm:7.9.0" @@ -13694,10 +13694,10 @@ __metadata: languageName: node linkType: hard -"core-js@npm:3.38.0, core-js@npm:^3.6.0, core-js@npm:^3.8.3": - version: 3.38.0 - resolution: "core-js@npm:3.38.0" - checksum: 10/95f5c768ee14aaf79e8fece9e58023a5a6367186184c92e825a842f271d3d91c559cfadee9c75712c463f248c61d636ed5a31a1fff1c904d4f5a2ed69b23f0c2 +"core-js@npm:3.38.1, core-js@npm:^3.6.0, core-js@npm:^3.8.3": + version: 3.38.1 + resolution: "core-js@npm:3.38.1" + checksum: 10/3c25fdf0b2595ed37ceb305213a61e2cf26185f628455e99d1c736dda5f69e2de4de7126e6a1da136f54260c4fcc982c4215e37b5a618790a597930f854c0a37 languageName: node linkType: hard @@ -18290,7 +18290,7 @@ __metadata: comlink: "npm:4.4.1" common-tags: "npm:1.8.2" copy-webpack-plugin: "npm:12.0.2" - core-js: "npm:3.38.0" + core-js: "npm:3.38.1" css-loader: "npm:7.1.2" css-minimizer-webpack-plugin: "npm:6.0.0" cypress: "npm:13.10.0" From 4d82139a333428f6d0ca266708be3e4e7d0dc200 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 21 Aug 2024 12:46:30 +0100 Subject: [PATCH 186/229] Chore: Migrate `_button` SCSS styles to emotion globals (#92135) migrate button styles to emotion globals --- .../src/themes/GlobalStyles/utilityClasses.ts | 99 +++++ public/sass/_grafana.scss | 1 - public/sass/components/_buttons.scss | 415 ------------------ 3 files changed, 99 insertions(+), 416 deletions(-) delete mode 100644 public/sass/components/_buttons.scss diff --git a/packages/grafana-ui/src/themes/GlobalStyles/utilityClasses.ts b/packages/grafana-ui/src/themes/GlobalStyles/utilityClasses.ts index f931952116c..ac14021ea41 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/utilityClasses.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/utilityClasses.ts @@ -2,6 +2,37 @@ import { css } from '@emotion/react'; import { GrafanaTheme2 } from '@grafana/data'; +function buttonBackgroundMixin( + startColor: string, + endColor: string, + textColor = '#fff', + textShadow = '0px 1px 0 rgba(0, 0, 0, 0.1)' +) { + return { + backgroundColor: startColor, + backgroundImage: `linear-gradient(to bottom, ${startColor}, ${endColor})`, + backgroundRepeat: 'repeat-x', + color: textColor, + textShadow: textShadow, + borderColor: startColor, + + // in these cases the gradient won't cover the background, so we override + '&:hover, &:focus, &:active, &.active, &.disabled, &[disabled]': { + color: textColor, + backgroundImage: 'none', + backgroundColor: startColor, + }, + }; +} + +function buttonSizeMixin(paddingY: string, paddingX: string, fontSize: string, borderRadius: string) { + return { + padding: `${paddingY} ${paddingX}`, + fontSize: fontSize, + borderRadius: borderRadius, + }; +} + export function getUtilityClassStyles(theme: GrafanaTheme2) { return css({ '.highlight-word': { @@ -38,5 +69,73 @@ export function getUtilityClassStyles(theme: GrafanaTheme2) { justifyContent: 'center', justifyItems: 'center', }, + '.btn': { + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + fontWeight: theme.typography.fontWeightMedium, + lineHeight: theme.typography.body.lineHeight, + textAlign: 'center', + verticalAlign: 'middle', + cursor: 'pointer', + border: 'none', + height: `${theme.spacing.gridSize * theme.components.height.md}px`, + ...buttonSizeMixin( + theme.spacing(0), + theme.spacing(2), + `${theme.typography.fontSize}px`, + theme.shape.radius.default + ), + + '&, &:active, &.active': { + '&:focus, &.focus': { + outline: 'none', + }, + }, + '&:focus, &:hover': { + textDecoration: 'none', + }, + '&.focus': { + textDecoration: 'none', + }, + '&:active, &.active': { + backgroundImage: 'none', + outline: 0, + }, + '&.disabled, &[disabled], &:disabled': { + cursor: 'not-allowed', + opacity: 0.65, + boxShadow: 'none', + pointerEvents: 'none', + }, + }, + '.btn-small': { + ...buttonSizeMixin(theme.spacing(0.5), theme.spacing(1), theme.typography.size.sm, theme.shape.radius.default), + height: `${theme.spacing.gridSize * theme.components.height.sm}px`, + }, + // Deprecated, only used by old plugins + '.btn-mini': { + ...buttonSizeMixin(theme.spacing(0.5), theme.spacing(1), theme.typography.size.sm, theme.shape.radius.default), + height: `${theme.spacing.gridSize * theme.components.height.sm}px`, + }, + '.btn-success, .btn-primary': { + ...buttonBackgroundMixin(theme.colors.primary.main, theme.colors.primary.shade), + }, + '.btn-danger': { + ...buttonBackgroundMixin(theme.colors.error.main, theme.colors.error.shade), + }, + '.btn-secondary': { + ...buttonBackgroundMixin(theme.colors.secondary.main, theme.colors.secondary.shade, theme.colors.text.primary), + }, + '.btn-inverse': { + ...buttonBackgroundMixin( + theme.isDark ? theme.v1.palette.dark6 : theme.v1.palette.gray5, + theme.isDark ? theme.v1.palette.dark5 : theme.v1.palette.gray4, + theme.colors.text.primary + ), + '&': { + boxShadow: 'none', + }, + }, }); } diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index 524357a008c..cd1f36c9bfc 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -8,7 +8,6 @@ @import 'utils/widths'; // COMPONENTS -@import 'components/buttons'; @import 'components/dropdown'; // ANGULAR diff --git a/public/sass/components/_buttons.scss b/public/sass/components/_buttons.scss deleted file mode 100644 index 5af22a2a797..00000000000 --- a/public/sass/components/_buttons.scss +++ /dev/null @@ -1,415 +0,0 @@ -@use 'sass:color'; -@use 'sass:map'; - -// Gradient Bar Colors for buttons and alerts -@mixin gradientBar($primaryColor, $secondaryColor, $text-color: #fff, $textShadow: 0 -1px 0 rgba(0, 0, 0, 0.25)) { - background-color: color.mix($primaryColor, $secondaryColor, 60%); - background-image: linear-gradient(to bottom, $primaryColor, $secondaryColor); // Standard, IE10 - background-repeat: repeat-x; - color: $text-color; - text-shadow: $textShadow; - border-color: $primaryColor; -} - -@mixin hover { - @if $enable-hover-media-query { - // See Media Queries Level 4: http://drafts.csswg.org/mediaqueries/#hover - // Currently shimmed by https://github.com/twbs/mq4-hover-shim - @media (hover: hover) { - &:hover { - @content; - } - } - } @else { - &:hover { - @content; - } - } -} - -@mixin hover-focus { - @if $enable-hover-media-query { - &:focus { - @content; - } - @include hover { - @content; - } - } @else { - &:focus, - &:hover { - @content; - } - } -} - -// Button backgrounds -// ------------------ -@mixin buttonBackground($startColor, $endColor, $text-color: #fff, $textShadow: 0px 1px 0 rgba(0, 0, 0, 0.1)) { - // gradientBar will set the background to a pleasing blend of these, to support IE<=9 - @include gradientBar($startColor, $endColor, $text-color, $textShadow); - - // in these cases the gradient won't cover the background, so we override - &:hover, - &:focus, - &:active, - &.active, - &.disabled, - &[disabled] { - color: $text-color; - background-image: none; - background-color: $startColor; - } -} - -// Button sizes -@mixin button-size($padding-y, $padding-x, $font-size, $border-radius) { - padding: $padding-y $padding-x; - font-size: $font-size; - //box-shadow: inset 0 (-$padding-y/3) rgba(0,0,0,0.15); - - border-radius: $border-radius; -} - -@mixin button-outline-variant($color) { - color: $white; - background-image: none; - background-color: transparent; - border: 1px solid $white; - - @include hover { - color: $white; - background-color: $color; - } - - &:focus, - &.focus { - color: $white; - background-color: $color; - } - - &:active, - &.active, - .open > &.dropdown-toggle { - color: $white; - background-color: $color; - - &:hover, - &:focus, - &.focus { - color: $white; - background-color: color.adjust($color, $lightness: -17%); - border-color: color.adjust($color, $lightness: -25%); - } - } - - &.disabled, - &:disabled { - &:focus, - &.focus { - border-color: color.adjust($color, $lightness: 20%); - } - @include hover { - border-color: color.adjust($color, $lightness: 20%); - } - } -} - -// -// Buttons -// -------------------------------------------------- - -// Base styles -// -------------------------------------------------- - -// Core -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - font-weight: $btn-font-weight; - line-height: $btn-line-height; - font-size: $font-size-base; - text-align: center; - vertical-align: middle; - cursor: pointer; - border: none; - height: $height-md + px; - - @include button-size($btn-padding-y, $space-md, $font-size-base, $border-radius-sm); - - &, - &:active, - &.active { - &:focus, - &.focus { - outline: none; - } - } - - @include hover-focus { - text-decoration: none; - } - &.focus { - text-decoration: none; - } - - &:active, - &.active { - background-image: none; - outline: 0; - } - - &.disabled, - &[disabled], - &:disabled { - cursor: $cursor-disabled; - opacity: 0.65; - box-shadow: none; - pointer-events: none; - } - - &--radius-left-0 { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - } - - &--radius-right-0 { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - } -} - -// Button Sizes -// -------------------------------------------------- - -// Large -.btn-large { - @include button-size($btn-padding-y-lg, $space-lg, $font-size-lg, $border-radius-sm); - font-weight: normal; - height: $height-lg + px; - - .gicon { - //font-size: 31px; - margin-right: $space-sm; - filter: brightness(100); - } -} - -.btn-small { - @include button-size($btn-padding-y-sm, $space-sm, $font-size-sm, $border-radius-sm); - height: $height-sm + px; -} - -// Deprecated, only used by old plugins -.btn-mini { - @include button-size($btn-padding-y-sm, $space-sm, $font-size-sm, $border-radius-sm); - height: #{height-sm}px; -} - -.btn-link { - color: $btn-link-color; - background: transparent; -} - -// Set the backgrounds -// ------------------------- -.btn-success, -.btn-primary { - @include buttonBackground($btn-primary-bg, $btn-primary-bg-hl); -} - -.btn-secondary { - @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl); -} - -// Danger and error appear as red -.btn-danger { - @include buttonBackground($btn-danger-bg, $btn-danger-bg-hl); -} - -// Info appears as a neutral blue -.btn-secondary { - @include buttonBackground($btn-secondary-bg, $btn-secondary-bg-hl, $text-color); - // Inverse appears as dark gray -} -.btn-inverse { - @include buttonBackground($btn-inverse-bg, $btn-inverse-bg-hl, $btn-inverse-text-color, $btn-inverse-text-shadow); - //background: $card-background; - /* stylelint-disable-next-line */ - & { - box-shadow: $card-shadow; - } - //border: 1px solid $tight-form-func-highlight-bg; -} - -.btn-transparent { - background-color: transparent; -} - -.btn-outline-primary { - @include button-outline-variant($btn-primary-bg); -} -.btn-outline-secondary { - @include button-outline-variant($btn-secondary-bg-hl); -} -.btn-outline-inverse { - @include button-outline-variant($btn-inverse-bg); -} -.btn-outline-danger { - @include button-outline-variant($btn-danger-bg); -} - -.btn-outline-disabled { - @include button-outline-variant($gray-1); - /* stylelint-disable-next-line */ - & { - box-shadow: none; - cursor: default; - } - - &:hover, - &:active, - &:active:hover, - &:focus { - color: $gray-1; - background-color: transparent; - border-color: $gray-1; - } -} - -// Extra padding -.btn-p-x-2 { - padding-left: 20px; - padding-right: 20px; -} - -// No horizontal padding -.btn-p-x-0 { - padding-left: 0; - padding-right: 0; -} - -// External services -// Usage: -// - -$btn-service-icon-width: 35px; -.btn-service { - position: relative; -} - -@each $service, $data in $external-services { - $serviceBgColor: map.get($data, bgColor); - $serviceBorderColor: map.get($data, borderColor); - - .btn-service--#{$service} { - background-color: $serviceBgColor; - border: 1px solid $serviceBorderColor; - - .btn-service-icon { - font-size: 24px; // Override - border-right: 1px solid $serviceBorderColor; - } - } -} - -.btn-service-icon { - position: absolute; - left: 0; - height: 100%; - top: 0; - padding-left: $space-sm; - padding-right: $space-sm; - width: $btn-service-icon-width; - text-align: center; - - &::before { - position: relative; - top: 4px; - } -} - -.btn-service--grafanacom { - .btn-service-icon { - background-image: url(../img/grafana_mask_icon_white.svg); - background-repeat: no-repeat; - background-position: 50%; - background-size: 60%; - } -} - -.btn-service--azuread { - .btn-service-icon { - background-image: url(../img/microsoft_auth_icon.svg); - background-repeat: no-repeat; - background-position: 50%; - background-size: 60%; - } -} - -.btn-service--okta { - .btn-service-icon { - background-image: url(../img/okta_logo_white.png); - background-repeat: no-repeat; - background-position: 50%; - background-size: 60%; - } -} - -//Toggle button - -.toggle-btn { - background: $input-label-bg; - color: $text-color-weak; - box-shadow: $card-shadow; - - &:first-child { - border-radius: 2px 0 0 2px; - margin: 0; - } - &:last-child { - border-radius: 0 2px 2px 0; - margin-left: 0 !important; - } - - &.active { - background-color: color.adjust($input-label-bg, $lightness: 5%); - color: $link-color; - &:hover { - cursor: default; - } - } -} - -//Button animations - -.btn-loading span { - animation-name: blink; - animation-duration: 1.4s; - animation-iteration-count: infinite; - animation-fill-mode: both; -} - -.btn-loading span:nth-child(2) { - animation-delay: 0.2s; -} - -.btn-loading span:nth-child(3) { - animation-delay: 0.4s; -} - -@keyframes blink { - 0% { - opacity: 0.2; - font-size: 14; - } - 20% { - opacity: 1; - font-size: 18; - } - 100% { - opacity: 0.2; - font-size: 14; - } -} From 9563c896ee618227ceb8b4e418b440e62f5ecdc1 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 21 Aug 2024 14:49:28 +0300 Subject: [PATCH 187/229] Playlists: limit the total number of playlists (#92132) --- .../playlist/playlistimpl/xorm_store.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pkg/services/playlist/playlistimpl/xorm_store.go b/pkg/services/playlist/playlistimpl/xorm_store.go index 3b3a1998eb6..07c1ce548fd 100644 --- a/pkg/services/playlist/playlistimpl/xorm_store.go +++ b/pkg/services/playlist/playlistimpl/xorm_store.go @@ -15,6 +15,8 @@ type sqlStore struct { db db.DB } +const MAX_PLAYLISTS = 1000 + var _ store = &sqlStore{} func (s *sqlStore) Insert(ctx context.Context, cmd *playlist.CreatePlaylistCommand) (*playlist.Playlist, error) { @@ -29,6 +31,14 @@ func (s *sqlStore) Insert(ctx context.Context, cmd *playlist.CreatePlaylistComma } err := s.db.WithTransactionalDbSession(ctx, func(sess *db.Session) error { + count, err := sess.SQL("SELECT COUNT(*) FROM playlist WHERE playlist.org_id = ?", cmd.OrgId).Count() + if err != nil { + return err + } + if count > MAX_PLAYLISTS { + return fmt.Errorf("too many playlists exist (%d > %d)", count, MAX_PLAYLISTS) + } + ts := time.Now().UnixMilli() p = playlist.Playlist{ Name: cmd.Name, @@ -39,7 +49,7 @@ func (s *sqlStore) Insert(ctx context.Context, cmd *playlist.CreatePlaylistComma UpdatedAt: ts, } - _, err := sess.Insert(&p) + _, err = sess.Insert(&p) if err != nil { return err } @@ -166,6 +176,10 @@ func (s *sqlStore) List(ctx context.Context, query *playlist.GetPlaylistsQuery) return playlists, playlist.ErrCommandValidationFailed } + if query.Limit > MAX_PLAYLISTS || query.Limit < 1 { + query.Limit = MAX_PLAYLISTS + } + err := s.db.WithDbSession(ctx, func(dbSess *db.Session) error { sess := dbSess.Limit(query.Limit) @@ -185,7 +199,7 @@ func (s *sqlStore) ListAll(ctx context.Context, orgId int64) ([]playlist.Playlis db := s.db.GetSqlxSession() // OK because dates are numbers! playlists := []playlist.PlaylistDTO{} - err := db.Select(ctx, &playlists, "SELECT * FROM playlist WHERE org_id=? ORDER BY created_at asc", orgId) + err := db.Select(ctx, &playlists, "SELECT * FROM playlist WHERE org_id=? ORDER BY created_at asc LIMIT ?", orgId, MAX_PLAYLISTS) if err != nil { return nil, err } From 237f8c7bac1bbecf9415e339c1afc18034663f0d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 11:35:12 +0000 Subject: [PATCH 188/229] Update dependency downshift to v9.0.8 --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index b6bb178c41f..96966fd998e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15432,8 +15432,8 @@ __metadata: linkType: hard "downshift@npm:^9.0.6": - version: 9.0.7 - resolution: "downshift@npm:9.0.7" + version: 9.0.8 + resolution: "downshift@npm:9.0.8" dependencies: "@babel/runtime": "npm:^7.24.5" compute-scroll-into-view: "npm:^3.1.0" @@ -15442,7 +15442,7 @@ __metadata: tslib: "npm:^2.6.2" peerDependencies: react: ">=16.12.0" - checksum: 10/d4adf8e0c36b911d751aaca01ca517c4f8c467f50f4340cc92a491153ada54da613a44cccb6c942fb95f38a75bbb6172c52c5ffc8935ea9ee2a97599563ed6fe + checksum: 10/9dc4577e780c54742ba4dde11f481f0d839f001b309200fbe4db112385b227ccd9cd2ef97d9e995379fa70249f0664a562240e415b9966f18c8a5cb7ce435f2c languageName: node linkType: hard From 8d725a641cb8368c8253f7c8c1feb3cdafa515f2 Mon Sep 17 00:00:00 2001 From: Fayzal Ghantiwala <114010985+fayzal-g@users.noreply.github.com> Date: Wed, 21 Aug 2024 13:06:01 +0100 Subject: [PATCH 189/229] Alerting: Integration test for testing template via remote alertmanager (#92147) * Add integration test for testing template * Update drone signature --- .drone.yml | 16 ++--- .../blocks/mimir_backend/docker-compose.yaml | 4 +- .../ngalert/remote/alertmanager_test.go | 61 +++++++++++++++++++ scripts/drone/utils/images.star | 2 +- 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/.drone.yml b/.drone.yml index 22b31b9b296..861c6d08fc0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -954,7 +954,7 @@ services: - commands: - /bin/mimir -target=backend -alertmanager.grafana-alertmanager-compatibility-enabled environment: {} - image: grafana/mimir-alpine:r295-a23e559 + image: grafana/mimir-alpine:r304-3872ccb name: mimir_backend - environment: {} image: redis:6.2.11-alpine @@ -1402,7 +1402,7 @@ services: - commands: - /bin/mimir -target=backend -alertmanager.grafana-alertmanager-compatibility-enabled environment: {} - image: grafana/mimir-alpine:r295-a23e559 + image: grafana/mimir-alpine:r304-3872ccb name: mimir_backend - environment: {} image: redis:6.2.11-alpine @@ -2480,7 +2480,7 @@ services: - commands: - /bin/mimir -target=backend -alertmanager.grafana-alertmanager-compatibility-enabled environment: {} - image: grafana/mimir-alpine:r295-a23e559 + image: grafana/mimir-alpine:r304-3872ccb name: mimir_backend - environment: {} image: redis:6.2.11-alpine @@ -3112,7 +3112,7 @@ services: - commands: - /bin/mimir -target=backend -alertmanager.grafana-alertmanager-compatibility-enabled environment: {} - image: grafana/mimir-alpine:r295-a23e559 + image: grafana/mimir-alpine:r304-3872ccb name: mimir_backend - environment: {} image: redis:6.2.11-alpine @@ -5271,7 +5271,7 @@ services: - commands: - /bin/mimir -target=backend -alertmanager.grafana-alertmanager-compatibility-enabled environment: {} - image: grafana/mimir-alpine:r295-a23e559 + image: grafana/mimir-alpine:r304-3872ccb name: mimir_backend - environment: {} image: redis:6.2.11-alpine @@ -5792,7 +5792,7 @@ steps: - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM plugins/slack - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM python:3.8 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM postgres:12.3-alpine - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/mimir-alpine:r295-a23e559 + - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM grafana/mimir-alpine:r304-3872ccb - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM mysql:5.7.39 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM mysql:8.0.32 - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM redis:6.2.11-alpine @@ -5829,7 +5829,7 @@ steps: - trivy --exit-code 1 --severity HIGH,CRITICAL plugins/slack - trivy --exit-code 1 --severity HIGH,CRITICAL python:3.8 - trivy --exit-code 1 --severity HIGH,CRITICAL postgres:12.3-alpine - - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/mimir-alpine:r295-a23e559 + - trivy --exit-code 1 --severity HIGH,CRITICAL grafana/mimir-alpine:r304-3872ccb - trivy --exit-code 1 --severity HIGH,CRITICAL mysql:5.7.39 - trivy --exit-code 1 --severity HIGH,CRITICAL mysql:8.0.32 - trivy --exit-code 1 --severity HIGH,CRITICAL redis:6.2.11-alpine @@ -6074,6 +6074,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: d35eadf9a166f68973ffd6df85f165bbda468d422d5debe416ec6a5af6ead84a +hmac: 39565be86e2be3f42728062ce9c4e4b28d5f386e5f48cf3c765fd443c44d9e0e ... diff --git a/devenv/docker/blocks/mimir_backend/docker-compose.yaml b/devenv/docker/blocks/mimir_backend/docker-compose.yaml index 2297d16acc6..a09635e80e3 100644 --- a/devenv/docker/blocks/mimir_backend/docker-compose.yaml +++ b/devenv/docker/blocks/mimir_backend/docker-compose.yaml @@ -1,5 +1,5 @@ mimir_backend: - image: grafana/mimir-alpine:r295-a23e559 + image: grafana/mimir-alpine:r304-3872ccb container_name: mimir_backend command: - -target=backend @@ -14,4 +14,4 @@ - 8080:8080 volumes: - "./docker/blocks/mimir_backend/nginx/nginx.conf.template:/etc/nginx/templates/nginx.conf.template" - - "./docker/blocks/mimir_backend/nginx/.htpasswd:/etc/nginx/.htpasswd" \ No newline at end of file + - "./docker/blocks/mimir_backend/nginx/.htpasswd:/etc/nginx/.htpasswd" diff --git a/pkg/services/ngalert/remote/alertmanager_test.go b/pkg/services/ngalert/remote/alertmanager_test.go index b04c7194ee0..51e9361e5ab 100644 --- a/pkg/services/ngalert/remote/alertmanager_test.go +++ b/pkg/services/ngalert/remote/alertmanager_test.go @@ -22,6 +22,7 @@ import ( "github.com/grafana/alerting/definition" alertingModels "github.com/grafana/alerting/models" + "github.com/grafana/alerting/notify" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" @@ -739,6 +740,66 @@ func TestIntegrationRemoteAlertmanagerReceivers(t *testing.T) { }, rcvs) } +func TestIntegrationRemoteAlertmanagerTestTemplates(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + amURL, ok := os.LookupEnv("AM_URL") + if !ok { + t.Skip("No Alertmanager URL provided") + } + + tenantID := os.Getenv("AM_TENANT_ID") + password := os.Getenv("AM_PASSWORD") + + cfg := AlertmanagerConfig{ + OrgID: 1, + URL: amURL, + TenantID: tenantID, + BasicAuthPassword: password, + DefaultConfig: defaultGrafanaConfig, + } + + secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) + m := metrics.NewRemoteAlertmanagerMetrics(prometheus.NewRegistry()) + am, err := NewAlertmanager(cfg, nil, secretsService.Decrypt, NoopAutogenFn, m, tracing.InitializeTracerForTest()) + require.NoError(t, err) + + // Valid template + c := apimodels.TestTemplatesConfigBodyParams{ + Alerts: []*amv2.PostableAlert{ + { + Annotations: amv2.LabelSet{ + "annotations_label": "annotations_value", + }, + Alert: amv2.Alert{ + Labels: amv2.LabelSet{ + "labels_label:": "labels_value", + }, + }, + }, + }, + Template: `{{ define "test" }} {{ index .Alerts 0 }} {{ end }}`, + Name: "test", + } + res, err := am.TestTemplate(context.Background(), c) + + require.NoError(t, err) + require.Len(t, res.Errors, 0) + require.Len(t, res.Results, 1) + require.Equal(t, "test", res.Results[0].Name) + + // Invalid template + c.Template = `{{ define "test" }} {{ index 0 .Alerts }} {{ end }}` + res, err = am.TestTemplate(context.Background(), c) + + require.NoError(t, err) + require.Len(t, res.Results, 0) + require.Len(t, res.Errors, 1) + require.Equal(t, notify.ExecutionError, res.Errors[0].Kind) +} + func genAlert(active bool, labels map[string]string) amv2.PostableAlert { endsAt := time.Now() if active { diff --git a/scripts/drone/utils/images.star b/scripts/drone/utils/images.star index ec757c4a76f..a213e08c62c 100644 --- a/scripts/drone/utils/images.star +++ b/scripts/drone/utils/images.star @@ -22,7 +22,7 @@ images = { "plugins_slack": "plugins/slack", "python": "python:3.8", "postgres_alpine": "postgres:12.3-alpine", - "mimir": "grafana/mimir-alpine:r295-a23e559", + "mimir": "grafana/mimir-alpine:r304-3872ccb", "mysql5": "mysql:5.7.39", "mysql8": "mysql:8.0.32", "redis_alpine": "redis:6.2.11-alpine", From 4bc64dd74798f9d8bd1c2e9c163c74301e8f8bc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 21 Aug 2024 14:44:12 +0200 Subject: [PATCH 190/229] Page/Body: Only show scrollbar lane gutter when needed (#92186) * Page/Body: Only show scrollbar lane gutter when needed * Update packages/grafana-ui/src/themes/GlobalStyles/elements.ts Co-authored-by: Ashley Harrison --------- Co-authored-by: Ashley Harrison --- packages/grafana-ui/src/themes/GlobalStyles/elements.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts index 6586dbb6e0d..58b63a8e1c1 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/elements.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/elements.ts @@ -35,7 +35,7 @@ export function getElementStyles(theme: GrafanaTheme2) { // Need type assertion here due to the use of !important // see https://github.com/frenic/csstype/issues/114#issuecomment-697201978 // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - overflowY: 'scroll !important' as 'scroll', + overflowY: 'auto !important' as 'auto', paddingRight: '0 !important', '@media print': { overflow: 'visible', From 1b6db3da22b24cc026c5234ccbf6f3dd05e9e9b7 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Wed, 21 Aug 2024 15:07:01 +0200 Subject: [PATCH 191/229] Loki language provider: don't cache empty array while querying (#92092) * Loki language provider: don't cache empty array while querying * Prettier * Unfocus test * chore: add extra assertion --- .../datasource/loki/LanguageProvider.test.ts | 18 +++++++++ .../datasource/loki/LanguageProvider.ts | 37 ++++++++++++++----- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/public/app/plugins/datasource/loki/LanguageProvider.test.ts b/public/app/plugins/datasource/loki/LanguageProvider.test.ts index 5a63f43c2e2..9371a451e37 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.test.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.test.ts @@ -268,6 +268,24 @@ describe('Language completion provider', () => { end: expect.any(Number), }); }); + + it('should use a single promise to resolve values', async () => { + const datasource = setup({ testkey: ['label1_val1', 'label1_val2'], label2: [] }); + const provider = await getLanguageProvider(datasource); + const requestSpy = jest.spyOn(provider, 'request'); + const promise1 = provider.fetchLabelValues('testkey'); + const promise2 = provider.fetchLabelValues('testkey'); + const promise3 = provider.fetchLabelValues('testkeyNOPE'); + expect(requestSpy).toHaveBeenCalledTimes(2); + + const values1 = await promise1; + const values2 = await promise2; + const values3 = await promise3; + + expect(values1).toStrictEqual(values2); + expect(values2).not.toStrictEqual(values3); + expect(requestSpy).toHaveBeenCalledTimes(2); + }); }); describe('fetchLabels', () => { diff --git a/public/app/plugins/datasource/loki/LanguageProvider.ts b/public/app/plugins/datasource/loki/LanguageProvider.ts index b833308422d..20f79dfa375 100644 --- a/public/app/plugins/datasource/loki/LanguageProvider.ts +++ b/public/app/plugins/datasource/loki/LanguageProvider.ts @@ -31,6 +31,7 @@ export default class LokiLanguageProvider extends LanguageProvider { */ private seriesCache = new LRUCache>({ max: 10 }); private labelsCache = new LRUCache({ max: 10 }); + private labelsPromisesCache = new LRUCache>({ max: 10 }); constructor(datasource: LokiDatasource, initialValues?: any) { super(); @@ -272,18 +273,34 @@ export default class LokiLanguageProvider extends LanguageProvider { const cacheKey = this.generateCacheKey(url, start, end, paramCacheKey); - let labelValues = this.labelsCache.get(cacheKey); - if (!labelValues) { - // Clear value when requesting new one. Empty object being truthy also makes sure we don't request twice. - this.labelsCache.set(cacheKey, []); - const res = await this.request(url, params); - if (Array.isArray(res)) { - labelValues = res.slice().sort(); - this.labelsCache.set(cacheKey, labelValues); - } + // Values in cache, return + const labelValues = this.labelsCache.get(cacheKey); + if (labelValues) { + return labelValues; + } + + // Promise in cache, return + let labelValuesPromise = this.labelsPromisesCache.get(cacheKey); + if (labelValuesPromise) { + return labelValuesPromise; } - return labelValues ?? []; + labelValuesPromise = new Promise(async (resolve) => { + try { + const data = await this.request(url, params); + if (Array.isArray(data)) { + const labelValues = data.slice().sort(); + this.labelsCache.set(cacheKey, labelValues); + this.labelsPromisesCache.delete(cacheKey); + resolve(labelValues); + } + } catch (error) { + console.error(error); + resolve([]); + } + }); + this.labelsPromisesCache.set(cacheKey, labelValuesPromise); + return labelValuesPromise; } /** From 302bfe3edf44622c8901a7b60531b0ac0c4c6f02 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:10:37 +0000 Subject: [PATCH 192/229] Update dependency i18next-parser to v9.0.2 --- package.json | 2 +- yarn.lock | 361 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 336 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 30e5b842012..563c24a39b4 100644 --- a/package.json +++ b/package.json @@ -190,7 +190,7 @@ "html-loader": "5.1.0", "html-webpack-plugin": "5.6.0", "http-server": "14.1.1", - "i18next-parser": "9.0.1", + "i18next-parser": "9.0.2", "ini": "^4.1.3", "jest": "29.7.0", "jest-canvas-mock": "2.5.2", diff --git a/yarn.lock b/yarn.lock index 96966fd998e..979a0410b4d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2213,6 +2213,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/aix-ppc64@npm:0.23.1" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/android-arm64@npm:0.20.2" @@ -2227,6 +2234,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-arm64@npm:0.23.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/android-arm@npm:0.20.2" @@ -2241,6 +2255,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-arm@npm:0.23.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/android-x64@npm:0.20.2" @@ -2255,6 +2276,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/android-x64@npm:0.23.1" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/darwin-arm64@npm:0.20.2" @@ -2269,6 +2297,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/darwin-arm64@npm:0.23.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/darwin-x64@npm:0.20.2" @@ -2283,6 +2318,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/darwin-x64@npm:0.23.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/freebsd-arm64@npm:0.20.2" @@ -2297,6 +2339,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/freebsd-arm64@npm:0.23.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/freebsd-x64@npm:0.20.2" @@ -2311,6 +2360,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/freebsd-x64@npm:0.23.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/linux-arm64@npm:0.20.2" @@ -2325,6 +2381,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-arm64@npm:0.23.1" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/linux-arm@npm:0.20.2" @@ -2339,6 +2402,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-arm@npm:0.23.1" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/linux-ia32@npm:0.20.2" @@ -2353,6 +2423,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-ia32@npm:0.23.1" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/linux-loong64@npm:0.20.2" @@ -2367,6 +2444,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-loong64@npm:0.23.1" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/linux-mips64el@npm:0.20.2" @@ -2381,6 +2465,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-mips64el@npm:0.23.1" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/linux-ppc64@npm:0.20.2" @@ -2395,6 +2486,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-ppc64@npm:0.23.1" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/linux-riscv64@npm:0.20.2" @@ -2409,6 +2507,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-riscv64@npm:0.23.1" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/linux-s390x@npm:0.20.2" @@ -2423,6 +2528,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-s390x@npm:0.23.1" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/linux-x64@npm:0.20.2" @@ -2437,6 +2549,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/linux-x64@npm:0.23.1" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/netbsd-x64@npm:0.20.2" @@ -2451,6 +2570,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/netbsd-x64@npm:0.23.1" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/openbsd-arm64@npm:0.23.1" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/openbsd-x64@npm:0.20.2" @@ -2465,6 +2598,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/openbsd-x64@npm:0.23.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/sunos-x64@npm:0.20.2" @@ -2479,6 +2619,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/sunos-x64@npm:0.23.1" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/win32-arm64@npm:0.20.2" @@ -2493,6 +2640,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-arm64@npm:0.23.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/win32-ia32@npm:0.20.2" @@ -2507,6 +2661,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-ia32@npm:0.23.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.20.2": version: 0.20.2 resolution: "@esbuild/win32-x64@npm:0.20.2" @@ -2521,6 +2682,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.23.1": + version: 0.23.1 + resolution: "@esbuild/win32-x64@npm:0.23.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -12889,18 +13057,22 @@ __metadata: languageName: node linkType: hard -"cheerio@npm:^1.0.0-rc.2": - version: 1.0.0-rc.12 - resolution: "cheerio@npm:1.0.0-rc.12" +"cheerio@npm:^1.0.0": + version: 1.0.0 + resolution: "cheerio@npm:1.0.0" dependencies: cheerio-select: "npm:^2.1.0" dom-serializer: "npm:^2.0.0" domhandler: "npm:^5.0.3" - domutils: "npm:^3.0.1" - htmlparser2: "npm:^8.0.1" - parse5: "npm:^7.0.0" + domutils: "npm:^3.1.0" + encoding-sniffer: "npm:^0.2.0" + htmlparser2: "npm:^9.1.0" + parse5: "npm:^7.1.2" parse5-htmlparser2-tree-adapter: "npm:^7.0.0" - checksum: 10/812fed61aa4b669bbbdd057d0d7f73ba4649cabfd4fc3a8f1d5c7499e4613b430636102716369cbd6bbed8f1bdcb06387ae8342289fb908b2743184775f94f18 + parse5-parser-stream: "npm:^7.1.2" + undici: "npm:^6.19.5" + whatwg-mimetype: "npm:^4.0.0" + checksum: 10/b535070add0f86b0a1f234274ad3ffb2c1c375c05b322d8057e89c3c797b3b4d2f05826c34a04df218bec9abf21b9f0d0bd71974a8dfe28b943fb87ab0170c38 languageName: node linkType: hard @@ -15339,7 +15511,7 @@ __metadata: languageName: node linkType: hard -"domhandler@npm:^5.0.1, domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": +"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3": version: 5.0.3 resolution: "domhandler@npm:5.0.3" dependencies: @@ -15380,14 +15552,14 @@ __metadata: languageName: node linkType: hard -"domutils@npm:^3.0.1": - version: 3.0.1 - resolution: "domutils@npm:3.0.1" +"domutils@npm:^3.0.1, domutils@npm:^3.1.0": + version: 3.1.0 + resolution: "domutils@npm:3.1.0" dependencies: dom-serializer: "npm:^2.0.0" domelementtype: "npm:^2.3.0" - domhandler: "npm:^5.0.1" - checksum: 10/c0031e4bf89bf701c552c6aa7937262351ae863d5bb0395ebae9cdb23eb3de0077343ca0ddfa63861d98c31c02bbabe4c6e0e11be87b04a090a4d5dbb75197dc + domhandler: "npm:^5.0.3" + checksum: 10/9a169a6e57ac4c738269a73ab4caf785114ed70e46254139c1bbc8144ac3102aacb28a6149508395ae34aa5d6a40081f4fa5313855dc8319c6d8359866b6dfea languageName: node linkType: hard @@ -15600,6 +15772,16 @@ __metadata: languageName: node linkType: hard +"encoding-sniffer@npm:^0.2.0": + version: 0.2.0 + resolution: "encoding-sniffer@npm:0.2.0" + dependencies: + iconv-lite: "npm:^0.6.3" + whatwg-encoding: "npm:^3.1.1" + checksum: 10/fe61a759dbef4d94ddc6f4fa645459897f4275eba04f0135d0459099b5f62fbba8a7ae57d23c9ec9b118c4c39ce056b51f1b8e62ad73a8ab365699448d655f4c + languageName: node + linkType: hard + "encoding@npm:^0.1.12, encoding@npm:^0.1.13": version: 0.1.13 resolution: "encoding@npm:0.1.13" @@ -15662,10 +15844,10 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.2.0, entities@npm:^4.4.0": - version: 4.4.0 - resolution: "entities@npm:4.4.0" - checksum: 10/b627cb900e901cc7817037b83bf993a1cbf6a64850540f7526af7bcf9c7d09ebc671198e6182cfae4680f733799e2852e6a1c46aa62ff36eb99680057a038df5 +"entities@npm:^4.2.0, entities@npm:^4.4.0, entities@npm:^4.5.0": + version: 4.5.0 + resolution: "entities@npm:4.5.0" + checksum: 10/ede2a35c9bce1aeccd055a1b445d41c75a14a2bb1cd22e242f20cf04d236cdcd7f9c859eb83f76885327bfae0c25bf03303665ee1ce3d47c5927b98b0e3e3d48 languageName: node linkType: hard @@ -15932,7 +16114,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:0.20.2, esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0, esbuild@npm:^0.20.1": +"esbuild@npm:0.20.2, esbuild@npm:^0.18.0 || ^0.19.0 || ^0.20.0": version: 0.20.2 resolution: "esbuild@npm:0.20.2" dependencies: @@ -16092,6 +16274,89 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.23.0": + version: 0.23.1 + resolution: "esbuild@npm:0.23.1" + dependencies: + "@esbuild/aix-ppc64": "npm:0.23.1" + "@esbuild/android-arm": "npm:0.23.1" + "@esbuild/android-arm64": "npm:0.23.1" + "@esbuild/android-x64": "npm:0.23.1" + "@esbuild/darwin-arm64": "npm:0.23.1" + "@esbuild/darwin-x64": "npm:0.23.1" + "@esbuild/freebsd-arm64": "npm:0.23.1" + "@esbuild/freebsd-x64": "npm:0.23.1" + "@esbuild/linux-arm": "npm:0.23.1" + "@esbuild/linux-arm64": "npm:0.23.1" + "@esbuild/linux-ia32": "npm:0.23.1" + "@esbuild/linux-loong64": "npm:0.23.1" + "@esbuild/linux-mips64el": "npm:0.23.1" + "@esbuild/linux-ppc64": "npm:0.23.1" + "@esbuild/linux-riscv64": "npm:0.23.1" + "@esbuild/linux-s390x": "npm:0.23.1" + "@esbuild/linux-x64": "npm:0.23.1" + "@esbuild/netbsd-x64": "npm:0.23.1" + "@esbuild/openbsd-arm64": "npm:0.23.1" + "@esbuild/openbsd-x64": "npm:0.23.1" + "@esbuild/sunos-x64": "npm:0.23.1" + "@esbuild/win32-arm64": "npm:0.23.1" + "@esbuild/win32-ia32": "npm:0.23.1" + "@esbuild/win32-x64": "npm:0.23.1" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10/f55fbd0bfb0f86ce67a6d2c6f6780729d536c330999ecb9f5a38d578fb9fda820acbbc67d6d1d377eed8fed50fc38f14ff9cb014f86dafab94269a7fb2177018 + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.1.2": version: 3.1.2 resolution: "escalade@npm:3.1.2" @@ -18332,7 +18597,7 @@ __metadata: http-server: "npm:14.1.1" i18next: "npm:^23.0.0" i18next-browser-languagedetector: "npm:^7.0.2" - i18next-parser: "npm:9.0.1" + i18next-parser: "npm:9.0.2" immer: "npm:10.1.1" immutable: "npm:4.3.7" ini: "npm:^4.1.3" @@ -18966,7 +19231,7 @@ __metadata: languageName: node linkType: hard -"htmlparser2@npm:^8.0.1, htmlparser2@npm:^8.0.2": +"htmlparser2@npm:^8.0.2": version: 8.0.2 resolution: "htmlparser2@npm:8.0.2" dependencies: @@ -18978,6 +19243,18 @@ __metadata: languageName: node linkType: hard +"htmlparser2@npm:^9.1.0": + version: 9.1.0 + resolution: "htmlparser2@npm:9.1.0" + dependencies: + domelementtype: "npm:^2.3.0" + domhandler: "npm:^5.0.3" + domutils: "npm:^3.1.0" + entities: "npm:^4.5.0" + checksum: 10/6352fa2a5495781fa9a02c9049908334cd068ff36d753870d30cd13b841e99c19646717567a2f9e9c44075bbe43d364e102f9d013a731ce962226d63746b794f + languageName: node + linkType: hard + "http-cache-semantics@npm:^4.1.0, http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" @@ -19192,17 +19469,17 @@ __metadata: languageName: node linkType: hard -"i18next-parser@npm:9.0.1": - version: 9.0.1 - resolution: "i18next-parser@npm:9.0.1" +"i18next-parser@npm:9.0.2": + version: 9.0.2 + resolution: "i18next-parser@npm:9.0.2" dependencies: "@babel/runtime": "npm:^7.23.2" broccoli-plugin: "npm:^4.0.7" - cheerio: "npm:^1.0.0-rc.2" + cheerio: "npm:^1.0.0" colors: "npm:1.4.0" commander: "npm:~12.1.0" eol: "npm:^0.9.1" - esbuild: "npm:^0.20.1" + esbuild: "npm:^0.23.0" fs-extra: "npm:^11.1.0" gulp-sort: "npm:^2.0.0" i18next: "npm:^23.5.1" @@ -19215,7 +19492,7 @@ __metadata: vinyl-fs: "npm:^4.0.0" bin: i18next: bin/cli.js - checksum: 10/d6f13c6cdc98f853b5cc433fb0853a996e9a88f83e9fe26974b4b6649a01713ec09f567869c57f21e57a7efcb731d50f296373f9647deef7a73d0d76fda63388 + checksum: 10/37c1ae7917f2c1b2ce91e27cb911aee2a5c3cc8a70ce0c40b2771c787376fcbda539293d835f1ae19222f5c1223027142567869d37eeae7a32cf71417a097405 languageName: node linkType: hard @@ -24766,6 +25043,15 @@ __metadata: languageName: node linkType: hard +"parse5-parser-stream@npm:^7.1.2": + version: 7.1.2 + resolution: "parse5-parser-stream@npm:7.1.2" + dependencies: + parse5: "npm:^7.0.0" + checksum: 10/75b232d460bce6bd0e35012750a78ef034f40ccf550b7c6cec3122395af6b4553202ad3663ad468cf537ead5a2e13b6727670395fd0ff548faccad1dc2dc93cf + languageName: node + linkType: hard + "parse5@npm:^7.0.0, parse5@npm:^7.1.1, parse5@npm:^7.1.2": version: 7.1.2 resolution: "parse5@npm:7.1.2" @@ -31210,6 +31496,13 @@ __metadata: languageName: node linkType: hard +"undici@npm:^6.19.5": + version: 6.19.8 + resolution: "undici@npm:6.19.8" + checksum: 10/19ae4ba38b029a664d99fd330935ef59136cf99edb04ed821042f27b5a9e84777265fb744c8a7abc83f2059afb019446c69a4ebef07bbc0ed6b2de8d67ef4090 + languageName: node + linkType: hard + "unicode-canonical-property-names-ecmascript@npm:^2.0.0": version: 2.0.0 resolution: "unicode-canonical-property-names-ecmascript@npm:2.0.0" @@ -32208,6 +32501,15 @@ __metadata: languageName: node linkType: hard +"whatwg-encoding@npm:^3.1.1": + version: 3.1.1 + resolution: "whatwg-encoding@npm:3.1.1" + dependencies: + iconv-lite: "npm:0.6.3" + checksum: 10/bbef815eb67f91487c7f2ef96329743f5fd8357d7d62b1119237d25d41c7e452dff8197235b2d3c031365a17f61d3bb73ca49d0ed1582475aa4a670815e79534 + languageName: node + linkType: hard + "whatwg-fetch@npm:3.6.20": version: 3.6.20 resolution: "whatwg-fetch@npm:3.6.20" @@ -32222,6 +32524,13 @@ __metadata: languageName: node linkType: hard +"whatwg-mimetype@npm:^4.0.0": + version: 4.0.0 + resolution: "whatwg-mimetype@npm:4.0.0" + checksum: 10/894a618e2d90bf444b6f309f3ceb6e58cf21b2beaa00c8b333696958c4076f0c7b30b9d33413c9ffff7c5832a0a0c8569e5bb347ef44beded72aeefd0acd62e8 + languageName: node + linkType: hard + "whatwg-url@npm:^11.0.0": version: 11.0.0 resolution: "whatwg-url@npm:11.0.0" From 2e60f28044f39c6f54353063e0650a075058592d Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 21 Aug 2024 16:30:17 +0300 Subject: [PATCH 193/229] Auth: remove id token flag (#92209) --- .../feature-toggles/index.md | 1 - .../src/types/featureToggles.gen.ts | 1 - pkg/api/pluginproxy/ds_proxy.go | 4 +--- pkg/api/pluginproxy/pluginproxy.go | 5 +--- pkg/apimachinery/identity/requester.go | 1 - pkg/services/auth/idimpl/service.go | 10 ++++---- pkg/services/auth/idimpl/service_test.go | 24 ++----------------- pkg/services/auth/idimpl/signer.go | 4 ---- pkg/services/authn/identity.go | 1 - pkg/services/featuremgmt/registry.go | 6 ----- pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 ---- pkg/services/featuremgmt/toggles_gen.json | 3 ++- .../clientmiddleware/forward_id_middleware.go | 4 +--- .../pluginsintegration/pluginsintegration.go | 5 +--- pkg/services/user/identity.go | 1 - 16 files changed, 12 insertions(+), 63 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 3dcb474dd6c..ce404a688f2 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -151,7 +151,6 @@ Experimental features might be changed or removed without prior notice. | `wargamesTesting` | Placeholder feature flag for internal testing | | `externalCorePlugins` | Allow core plugins to be loaded as external | | `pluginsAPIMetrics` | Sends metrics of public grafana packages usage by plugins | -| `idForwarding` | Generate signed id token for identity that can be forwarded to plugins and external services | | `enableNativeHTTPHistogram` | Enables native HTTP Histograms | | `disableClassicHTTPHistogram` | Disables classic HTTP Histogram (use with enableNativeHTTPHistogram) | | `kubernetesSnapshots` | Routes snapshot requests from /api to the /apis endpoint | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 1c577afa5f0..0d26cd877e6 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -106,7 +106,6 @@ export interface FeatureToggles { alertingInsights?: boolean; externalCorePlugins?: boolean; pluginsAPIMetrics?: boolean; - idForwarding?: boolean; externalServiceAccounts?: boolean; panelMonitoring?: boolean; enableNativeHTTPHistogram?: boolean; diff --git a/pkg/api/pluginproxy/ds_proxy.go b/pkg/api/pluginproxy/ds_proxy.go index 70d77d68bb0..746b3c9a3f4 100644 --- a/pkg/api/pluginproxy/ds_proxy.go +++ b/pkg/api/pluginproxy/ds_proxy.go @@ -271,9 +271,7 @@ func (proxy *DataSourceProxy) director(req *http.Request) { } } - if proxy.features.IsEnabled(req.Context(), featuremgmt.FlagIdForwarding) { - proxyutil.ApplyForwardIDHeader(req, proxy.ctx.SignedInUser) - } + proxyutil.ApplyForwardIDHeader(req, proxy.ctx.SignedInUser) } func (proxy *DataSourceProxy) validateRequest() error { diff --git a/pkg/api/pluginproxy/pluginproxy.go b/pkg/api/pluginproxy/pluginproxy.go index dd5f36a3cb9..8288c15276f 100644 --- a/pkg/api/pluginproxy/pluginproxy.go +++ b/pkg/api/pluginproxy/pluginproxy.go @@ -185,10 +185,7 @@ func (proxy PluginProxy) director(req *http.Request) { req.Header.Set("X-Grafana-Context", string(ctxJSON)) proxyutil.ApplyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser) - - if proxy.features.IsEnabled(req.Context(), featuremgmt.FlagIdForwarding) { - proxyutil.ApplyForwardIDHeader(req, proxy.ctx.SignedInUser) - } + proxyutil.ApplyForwardIDHeader(req, proxy.ctx.SignedInUser) if err := addHeaders(&req.Header, proxy.matchedRoute, data); err != nil { proxy.ctx.JsonApiErr(500, "Failed to render plugin headers", err) diff --git a/pkg/apimachinery/identity/requester.go b/pkg/apimachinery/identity/requester.go index ac44d236b29..5105a9dfb5f 100644 --- a/pkg/apimachinery/identity/requester.go +++ b/pkg/apimachinery/identity/requester.go @@ -73,7 +73,6 @@ type Requester interface { // HasUniqueId returns true if the entity has a unique id HasUniqueId() bool // GetIDToken returns a signed token representing the identity that can be forwarded to plugins and external services. - // Will only be set when featuremgmt.FlagIdForwarding is enabled. GetIDToken() string } diff --git a/pkg/services/auth/idimpl/service.go b/pkg/services/auth/idimpl/service.go index 7c75c86dc7f..3a9baa8b22d 100644 --- a/pkg/services/auth/idimpl/service.go +++ b/pkg/services/auth/idimpl/service.go @@ -18,7 +18,6 @@ import ( "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/auth" "github.com/grafana/grafana/pkg/services/authn" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/setting" ) @@ -31,8 +30,9 @@ const ( var _ auth.IDService = (*Service)(nil) func ProvideService( - cfg *setting.Cfg, signer auth.IDSigner, cache remotecache.CacheStorage, - features featuremgmt.FeatureToggles, authnService authn.Service, + cfg *setting.Cfg, signer auth.IDSigner, + cache remotecache.CacheStorage, + authnService authn.Service, reg prometheus.Registerer, ) *Service { s := &Service{ @@ -42,9 +42,7 @@ func ProvideService( nsMapper: request.GetNamespaceMapper(cfg), } - if features.IsEnabledGlobally(featuremgmt.FlagIdForwarding) { - authnService.RegisterPostAuthHook(s.hook, 140) - } + authnService.RegisterPostAuthHook(s.hook, 140) return s } diff --git a/pkg/services/auth/idimpl/service_test.go b/pkg/services/auth/idimpl/service_test.go index 61f5dbf6d66..20b1e1c0832 100644 --- a/pkg/services/auth/idimpl/service_test.go +++ b/pkg/services/auth/idimpl/service_test.go @@ -15,15 +15,12 @@ import ( "github.com/grafana/grafana/pkg/services/auth/idtest" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn/authntest" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/login" "github.com/grafana/grafana/pkg/setting" ) func Test_ProvideService(t *testing.T) { - t.Run("should register post auth hook when feature flag is enabled", func(t *testing.T) { - features := featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding) - + t.Run("should register post auth hook", func(t *testing.T) { var hookRegistered bool authnService := &authntest.MockService{ RegisterPostAuthHookFunc: func(_ authn.PostAuthHookFn, _ uint) { @@ -31,23 +28,9 @@ func Test_ProvideService(t *testing.T) { }, } - _ = ProvideService(setting.NewCfg(), nil, nil, features, authnService, nil) + _ = ProvideService(setting.NewCfg(), nil, nil, authnService, nil) assert.True(t, hookRegistered) }) - - t.Run("should not register post auth hook when feature flag is disabled", func(t *testing.T) { - features := featuremgmt.WithFeatures() - - var hookRegistered bool - authnService := &authntest.MockService{ - RegisterPostAuthHookFunc: func(_ authn.PostAuthHookFn, _ uint) { - hookRegistered = true - }, - } - - _ = ProvideService(setting.NewCfg(), nil, nil, features, authnService, nil) - assert.False(t, hookRegistered) - }) } func TestService_SignIdentity(t *testing.T) { @@ -67,7 +50,6 @@ func TestService_SignIdentity(t *testing.T) { t.Run("should sign identity", func(t *testing.T) { s := ProvideService( setting.NewCfg(), signer, remotecache.NewFakeCacheStorage(), - featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding), &authntest.FakeService{}, nil, ) token, _, err := s.SignIdentity(context.Background(), &authn.Identity{ID: "1", Type: claims.TypeUser}) @@ -78,7 +60,6 @@ func TestService_SignIdentity(t *testing.T) { t.Run("should sign identity with authenticated by if user is externally authenticated", func(t *testing.T) { s := ProvideService( setting.NewCfg(), signer, remotecache.NewFakeCacheStorage(), - featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding), &authntest.FakeService{}, nil, ) token, _, err := s.SignIdentity(context.Background(), &authn.Identity{ @@ -104,7 +85,6 @@ func TestService_SignIdentity(t *testing.T) { t.Run("should sign identity with authenticated by if user is externally authenticated", func(t *testing.T) { s := ProvideService( setting.NewCfg(), signer, remotecache.NewFakeCacheStorage(), - featuremgmt.WithFeatures(featuremgmt.FlagIdForwarding), &authntest.FakeService{}, nil, ) _, gotClaims, err := s.SignIdentity(context.Background(), &authn.Identity{ diff --git a/pkg/services/auth/idimpl/signer.go b/pkg/services/auth/idimpl/signer.go index 29d664e99b2..0a37b263f45 100644 --- a/pkg/services/auth/idimpl/signer.go +++ b/pkg/services/auth/idimpl/signer.go @@ -28,10 +28,6 @@ type LocalSigner struct { } func (s *LocalSigner) SignIDToken(ctx context.Context, claims *auth.IDClaims) (string, error) { - if !s.features.IsEnabled(ctx, featuremgmt.FlagIdForwarding) { - return "", nil - } - signer, err := s.getSigner(ctx) if err != nil { return "", err diff --git a/pkg/services/authn/identity.go b/pkg/services/authn/identity.go index e2afc31647a..4a0da892d8d 100644 --- a/pkg/services/authn/identity.go +++ b/pkg/services/authn/identity.go @@ -72,7 +72,6 @@ type Identity struct { // Permissions is the list of permissions the entity has. Permissions map[int64]map[string][]string // IDToken is a signed token representing the identity that can be forwarded to plugins and external services. - // Will only be set when featuremgmt.FlagIdForwarding is enabled. IDToken string IDTokenClaims *authn.Claims[authn.IDTokenClaims] } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index db598c1d193..ca00201458c 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -664,12 +664,6 @@ var ( Stage: FeatureStageExperimental, Owner: grafanaPluginsPlatformSquad, }, - { - Name: "idForwarding", - Description: "Generate signed id token for identity that can be forwarded to plugins and external services", - Stage: FeatureStageExperimental, - Owner: identityAccessTeam, - }, { Name: "externalServiceAccounts", Description: "Automatic service account and token setup for plugins", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index c9315a75b12..355fa7d25da 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -87,7 +87,6 @@ wargamesTesting,experimental,@grafana/hosted-grafana-team,false,false,false alertingInsights,GA,@grafana/alerting-squad,false,false,true externalCorePlugins,experimental,@grafana/plugins-platform-backend,false,false,false pluginsAPIMetrics,experimental,@grafana/plugins-platform-backend,false,false,true -idForwarding,experimental,@grafana/identity-access-team,false,false,false externalServiceAccounts,preview,@grafana/identity-access-team,false,false,false panelMonitoring,GA,@grafana/dataviz-squad,false,false,true enableNativeHTTPHistogram,experimental,@grafana/grafana-backend-services-squad,false,true,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index bd81612d795..89c2c2c1909 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -359,10 +359,6 @@ const ( // Sends metrics of public grafana packages usage by plugins FlagPluginsAPIMetrics = "pluginsAPIMetrics" - // FlagIdForwarding - // Generate signed id token for identity that can be forwarded to plugins and external services - FlagIdForwarding = "idForwarding" - // FlagExternalServiceAccounts // Automatic service account and token setup for plugins FlagExternalServiceAccounts = "externalServiceAccounts" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 40b7aad2501..a8a0ac0e195 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -1320,7 +1320,8 @@ "metadata": { "name": "idForwarding", "resourceVersion": "1718727528075", - "creationTimestamp": "2023-09-25T15:21:28Z" + "creationTimestamp": "2023-09-25T15:21:28Z", + "deletionTimestamp": "2024-08-21T11:35:56Z" }, "spec": { "description": "Generate signed id token for identity that can be forwarded to plugins and external services", diff --git a/pkg/services/pluginsintegration/clientmiddleware/forward_id_middleware.go b/pkg/services/pluginsintegration/clientmiddleware/forward_id_middleware.go index c43d6e5e62a..9e799759157 100644 --- a/pkg/services/pluginsintegration/clientmiddleware/forward_id_middleware.go +++ b/pkg/services/pluginsintegration/clientmiddleware/forward_id_middleware.go @@ -12,8 +12,7 @@ import ( const forwardIDHeaderName = "X-Grafana-Id" // NewForwardIDMiddleware creates a new plugins.ClientMiddleware that will -// set grafana id header on outgoing plugins.Client requests if the -// feature toggle FlagIdForwarding is enabled +// set grafana id header on outgoing plugins.Client requests func NewForwardIDMiddleware() plugins.ClientMiddleware { return plugins.ClientMiddlewareFunc(func(next plugins.Client) plugins.Client { return &ForwardIDMiddleware{ @@ -35,7 +34,6 @@ func (m *ForwardIDMiddleware) applyToken(ctx context.Context, pCtx backend.Plugi return nil } - // token will only be present if faeturemgmt.FlagIdForwarding is enabled if token := reqCtx.SignedInUser.GetIDToken(); token != "" { req.SetHTTPHeader(forwardIDHeaderName, token) } diff --git a/pkg/services/pluginsintegration/pluginsintegration.go b/pkg/services/pluginsintegration/pluginsintegration.go index bf0e7705fc0..e411e6c8552 100644 --- a/pkg/services/pluginsintegration/pluginsintegration.go +++ b/pkg/services/pluginsintegration/pluginsintegration.go @@ -187,12 +187,9 @@ func CreateMiddlewares(cfg *setting.Cfg, oAuthTokenService oauthtoken.OAuthToken clientmiddleware.NewCookiesMiddleware(skipCookiesNames), clientmiddleware.NewResourceResponseMiddleware(), clientmiddleware.NewCachingMiddlewareWithFeatureManager(cachingService, features), + clientmiddleware.NewForwardIDMiddleware(), ) - if features.IsEnabledGlobally(featuremgmt.FlagIdForwarding) { - middlewares = append(middlewares, clientmiddleware.NewForwardIDMiddleware()) - } - if cfg.SendUserHeader { middlewares = append(middlewares, clientmiddleware.NewUserHeaderMiddleware()) } diff --git a/pkg/services/user/identity.go b/pkg/services/user/identity.go index 83a303b28c8..c6939b47b0d 100644 --- a/pkg/services/user/identity.go +++ b/pkg/services/user/identity.go @@ -45,7 +45,6 @@ type SignedInUser struct { Permissions map[int64]map[string][]string `json:"-"` // IDToken is a signed token representing the identity that can be forwarded to plugins and external services. - // Will only be set when featuremgmt.FlagIdForwarding is enabled. IDToken string `json:"-" xorm:"-"` IDTokenClaims *authnlib.Claims[authnlib.IDTokenClaims] `json:"-" xorm:"-"` From 9a65d0a977546c6548b0f35a420714992edd6b6b Mon Sep 17 00:00:00 2001 From: Diego Augusto Molina Date: Wed, 21 Aug 2024 10:31:34 -0300 Subject: [PATCH 194/229] Storage: remove sqlstore.WrapDatabaseDriverWithHooks (#92111) remove sqlstore.WrapDatabaseDriverWithHooks from Unified Storage after related incident --- pkg/storage/unified/sql/db/dbimpl/dbEngine.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/storage/unified/sql/db/dbimpl/dbEngine.go b/pkg/storage/unified/sql/db/dbimpl/dbEngine.go index 9a303596ce2..c024c8e195a 100644 --- a/pkg/storage/unified/sql/db/dbimpl/dbEngine.go +++ b/pkg/storage/unified/sql/db/dbimpl/dbEngine.go @@ -8,7 +8,6 @@ import ( "github.com/go-sql-driver/mysql" "github.com/grafana/grafana/pkg/infra/tracing" - "github.com/grafana/grafana/pkg/services/sqlstore" "github.com/grafana/grafana/pkg/storage/unified/sql/db" "xorm.io/xorm" ) @@ -107,8 +106,7 @@ func getEnginePostgres(getter *sectionGetter, tracer tracing.Tracer) (*xorm.Engi } // FIXME: get rid of xorm - driverName := sqlstore.WrapDatabaseDriverWithHooks(db.DriverPostgres, tracer) - engine, err := xorm.NewEngine(driverName, dsn) + engine, err := xorm.NewEngine(db.DriverPostgres, dsn) if err != nil { return nil, fmt.Errorf("open database: %w", err) } From 81ce3c92d5b33f399785b8bc121ebff4ec003428 Mon Sep 17 00:00:00 2001 From: Leonor Oliveira <9090754+leonorfmartins@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:44:38 +0100 Subject: [PATCH 195/229] Remove optionsStorage and labelSelector (#92196) --- pkg/apiserver/rest/dualwriter_mode2.go | 38 ++++++++------------------ 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/pkg/apiserver/rest/dualwriter_mode2.go b/pkg/apiserver/rest/dualwriter_mode2.go index 3b611f6e1ed..86ab89e9cc0 100644 --- a/pkg/apiserver/rest/dualwriter_mode2.go +++ b/pkg/apiserver/rest/dualwriter_mode2.go @@ -138,17 +138,13 @@ func (d *DualWriterMode2) List(ctx context.Context, options *metainternalversion // Record the index of each LegacyStorage object so it can later be replaced by // an equivalent Storage object if it exists. - optionsStorage, indexMap, err := parseList(legacyList) + legacyNames, err := parseList(legacyList) if err != nil { return nil, err } - if optionsStorage.LabelSelector == nil { - return ll, nil - } - startStorage := time.Now() - sl, err := d.Storage.List(ctx, &optionsStorage) + sl, err := d.Storage.List(ctx, options) if err != nil { log.Error(err, "unable to list objects from storage") d.recordStorageDuration(true, mode2Str, d.kind, method, startStorage) @@ -163,14 +159,10 @@ func (d *DualWriterMode2) List(ctx context.Context, options *metainternalversion } for _, obj := range storageList { - accessor, err := meta.Accessor(obj) - if err != nil { - return nil, err - } - name := accessor.GetName() - if legacyIndex, ok := indexMap[name]; ok { - legacyList[legacyIndex] = obj - areEqual := Compare(obj, legacyList[legacyIndex]) + name := getName(obj) + if i, ok := legacyNames[name]; ok { + legacyList[i] = obj + areEqual := Compare(obj, legacyList[i]) d.recordOutcome(mode2Str, name, areEqual, method) if !areEqual { log.WithValues("name", name).Info("object from legacy and storage are not equal") @@ -212,16 +204,13 @@ func (d *DualWriterMode2) DeleteCollection(ctx context.Context, deleteValidation } // Only the items deleted by the legacy DeleteCollection call are selected for deletion by Storage. - optionsStorage, _, err := parseList(legacyList) + _, err = parseList(legacyList) if err != nil { return nil, err } - if optionsStorage.LabelSelector == nil { - return deleted, nil - } startStorage := time.Now() - res, err := d.Storage.DeleteCollection(ctx, deleteValidation, options, &optionsStorage) + res, err := d.Storage.DeleteCollection(ctx, deleteValidation, options, listOptions) if err != nil { log.WithValues("deleted", res).Error(err, "failed to delete collection successfully from Storage") d.recordStorageDuration(true, mode2Str, d.kind, method, startStorage) @@ -366,22 +355,17 @@ func (d *DualWriterMode2) ConvertToTable(ctx context.Context, object runtime.Obj return d.Storage.ConvertToTable(ctx, object, tableOptions) } -func parseList(legacyList []runtime.Object) (metainternalversion.ListOptions, map[string]int, error) { - options := metainternalversion.ListOptions{} - originKeys := []string{} +func parseList(legacyList []runtime.Object) (map[string]int, error) { indexMap := map[string]int{} for i, obj := range legacyList { accessor, err := utils.MetaAccessor(obj) if err != nil { - return options, nil, err + return nil, err } indexMap[accessor.GetName()] = i } - if len(originKeys) == 0 { - return options, nil, nil - } - return options, indexMap, nil + return indexMap, nil } func enrichLegacyObject(originalObj, returnedObj runtime.Object) error { From 801f2ba728c364df24c948f98dc3578bd6d61735 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 21 Aug 2024 16:05:07 +0200 Subject: [PATCH 196/229] DashboardScene: Support remember scroll position when coming back from view panel, panel edit and settings (#92185) * DashboardScene: Support remember scroll position when coming back from view panel, panel edit and settings * remove unused state prop * Update * Fixes * Update e2e --- .../general-dashboards.spec.ts | 6 +-- .../app/core/components/NativeScrollbar.tsx | 37 ++++++++++---- public/app/core/components/Page/Page.tsx | 11 ++-- public/app/core/components/Page/types.ts | 12 ++--- .../dashboard-scene/scene/DashboardScene.tsx | 22 ++++++++ .../scene/DashboardSceneRenderer.tsx | 21 ++++++-- .../dashboard/containers/DashboardPage.tsx | 51 ++++++++----------- 7 files changed, 97 insertions(+), 63 deletions(-) diff --git a/e2e/scenes/dashboards-suite/general-dashboards.spec.ts b/e2e/scenes/dashboards-suite/general-dashboards.spec.ts index 1edd34482b6..6291c787320 100644 --- a/e2e/scenes/dashboards-suite/general-dashboards.spec.ts +++ b/e2e/scenes/dashboards-suite/general-dashboards.spec.ts @@ -24,9 +24,7 @@ describe('Dashboards', () => { e2e.components.Panels.Panel.menuItems('Edit').click(); e2e.components.NavToolbar.editDashboard.backToDashboardButton().click(); - // And the last panel should still be visible! - // TODO: investigate scroll to on navigating back - // e2e.components.Panels.Panel.title('Panel #50').should('be.visible'); - // e2e.components.Panels.Panel.title('Panel #1').should('be.visible'); + // The last panel should still be visible! + e2e.components.Panels.Panel.title('Panel #50').should('be.visible'); }); }); diff --git a/public/app/core/components/NativeScrollbar.tsx b/public/app/core/components/NativeScrollbar.tsx index d0eb36adab7..9116f47b055 100644 --- a/public/app/core/components/NativeScrollbar.tsx +++ b/public/app/core/components/NativeScrollbar.tsx @@ -2,28 +2,35 @@ import { css, cx } from '@emotion/css'; import { useEffect, useRef } from 'react'; import { config } from '@grafana/runtime'; -import { CustomScrollbar, useStyles2 } from '@grafana/ui'; +import { useStyles2 } from '@grafana/ui'; -type Props = Parameters[0]; +export interface Props { + children: React.ReactNode; + onSetScrollRef?: (ref: ScrollRefElement) => void; + divId?: string; +} + +export interface ScrollRefElement { + scrollTop: number; + scrollTo: (x: number, y: number) => void; +} // Shim to provide API-compatibility for Page's scroll-related props // when bodyScrolling is enabled, this is a no-op // TODO remove this shim completely when bodyScrolling is enabled -export default function NativeScrollbar({ children, scrollRefCallback, scrollTop, divId }: Props) { +export default function NativeScrollbar({ children, onSetScrollRef, divId }: Props) { const styles = useStyles2(getStyles); const ref = useRef(null); useEffect(() => { - if (!config.featureToggles.bodyScrolling && ref.current && scrollRefCallback) { - scrollRefCallback(ref.current); + if (config.featureToggles.bodyScrolling && onSetScrollRef) { + onSetScrollRef(new WindowScrollElement()); } - }, [ref, scrollRefCallback]); - useEffect(() => { - if (!config.featureToggles.bodyScrolling && ref.current && scrollTop != null) { - ref.current?.scrollTo(0, scrollTop); + if (!config.featureToggles.bodyScrolling && ref.current && onSetScrollRef) { + onSetScrollRef(ref.current); } - }, [scrollTop]); + }, [ref, onSetScrollRef]); return config.featureToggles.bodyScrolling ? ( children @@ -35,6 +42,16 @@ export default function NativeScrollbar({ children, scrollRefCallback, scrollTop ); } +class WindowScrollElement { + public get scrollTop() { + return window.scrollY; + } + + public scrollTo(x: number, y: number) { + window.scrollTo(x, y); + } +} + function getStyles() { return { nativeScrollbars: css({ diff --git a/public/app/core/components/Page/Page.tsx b/public/app/core/components/Page/Page.tsx index 9056a7dfca5..0f82fd4ba8c 100644 --- a/public/app/core/components/Page/Page.tsx +++ b/public/app/core/components/Page/Page.tsx @@ -27,8 +27,7 @@ export const Page: PageType = ({ className, info, layout = PageLayoutType.Standard, - scrollTop, - scrollRef, + onSetScrollRef, ...otherProps }) => { const styles = useStyles2(getStyles); @@ -57,9 +56,7 @@ export const Page: PageType = ({
{pageHeaderNav && ( @@ -82,9 +79,7 @@ export const Page: PageType = ({
{children}
diff --git a/public/app/core/components/Page/types.ts b/public/app/core/components/Page/types.ts index f1df40ab280..cc83c18aade 100644 --- a/public/app/core/components/Page/types.ts +++ b/public/app/core/components/Page/types.ts @@ -1,8 +1,10 @@ -import { FC, HTMLAttributes, RefCallback } from 'react'; +import { FC, HTMLAttributes } from 'react'; import * as React from 'react'; import { NavModel, NavModelItem, PageLayoutType } from '@grafana/data'; +import { ScrollRefElement } from '../NativeScrollbar'; + import { PageContents } from './PageContents'; export interface PageProps extends HTMLAttributes { @@ -22,15 +24,11 @@ export interface PageProps extends HTMLAttributes { /** Control the page layout. */ layout?: PageLayoutType; /** + * TODO: Not sure we should deprecated it given the sidecar project? * @deprecated this will be removed when bodyScrolling is enabled by default * Can be used to get the scroll container element to access scroll position * */ - scrollRef?: RefCallback; - /** - * @deprecated this will be removed when bodyScrolling is enabled by default - * Can be used to update the current scroll position - * */ - scrollTop?: number; + onSetScrollRef?: (ref: ScrollRefElement) => void; } export interface PageInfoItem { diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index 8a63083655a..5274e75c459 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -27,6 +27,7 @@ import { } from '@grafana/scenes'; import { Dashboard, DashboardLink, LibraryPanel } from '@grafana/schema'; import appEvents from 'app/core/app_events'; +import { ScrollRefElement } from 'app/core/components/NativeScrollbar'; import { LS_PANEL_COPY_KEY } from 'app/core/constants'; import { getNavModel } from 'app/core/selectors/navModel'; import store from 'app/core/store'; @@ -167,6 +168,11 @@ export class DashboardScene extends SceneObjectBase { * A reference to the scopes facade */ private _scopesFacade: ScopesFacade | null; + /** + * Remember scroll position when going into panel edit + */ + private _scrollRef?: ScrollRefElement; + private _prevScrollPos?: number; public constructor(state: Partial) { super({ @@ -925,6 +931,22 @@ export class DashboardScene extends SceneObjectBase { } }); } + + public onSetScrollRef = (scrollElement: ScrollRefElement): void => { + this._scrollRef = scrollElement; + }; + + public rememberScrollPos() { + this._prevScrollPos = this._scrollRef?.scrollTop; + } + + public restoreScrollPos() { + if (this._prevScrollPos !== undefined) { + setTimeout(() => { + this._scrollRef?.scrollTo(0, this._prevScrollPos!); + }, 50); + } + } } export class DashboardVariableDependency implements SceneVariableDependencyConfigLike { diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx index 215d83f4dcc..39e7b1f47dc 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx @@ -1,4 +1,5 @@ import { css, cx } from '@emotion/css'; +import { useEffect, useMemo } from 'react'; import { useLocation } from 'react-router-dom'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; @@ -16,7 +17,7 @@ import { DashboardScene } from './DashboardScene'; import { NavToolbarActions } from './NavToolbarActions'; export function DashboardSceneRenderer({ model }: SceneComponentProps) { - const { controls, overlay, editview, editPanel, isEmpty, meta } = model.useState(); + const { controls, overlay, editview, editPanel, isEmpty, meta, viewPanelScene } = model.useState(); const headerHeight = useChromeHeaderHeight(); const styles = useStyles2(getStyles, headerHeight); const location = useLocation(); @@ -25,6 +26,21 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps { + if (viewPanelScene || isSettingsOpen || editPanel) { + model.rememberScrollPos(); + } + }, [isSettingsOpen, editPanel, viewPanelScene, model]); + + // Restore scroll pos when coming back + useEffect(() => { + if (!viewPanelScene && !isSettingsOpen && !editPanel) { + model.restoreScrollPos(); + } + }, [isSettingsOpen, editPanel, viewPanelScene, model]); if (editview) { return ( @@ -54,12 +70,11 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps {editPanel && } {!editPanel && ( - +
{controls && ( diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index e4f56d47b29..ecc58a535a6 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -7,6 +7,7 @@ import { selectors } from '@grafana/e2e-selectors'; import { config, locationService } from '@grafana/runtime'; import { Themeable2, withTheme2 } from '@grafana/ui'; import { notifyApp } from 'app/core/actions'; +import { ScrollRefElement } from 'app/core/components/NativeScrollbar'; import { Page } from 'app/core/components/Page/Page'; import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound'; import { GrafanaContext, GrafanaContextType } from 'app/core/context/GrafanaContext'; @@ -77,7 +78,7 @@ export interface State { showLoadingState: boolean; panelNotFound: boolean; editPanelAccessDenied: boolean; - scrollElement?: HTMLDivElement; + scrollElement?: ScrollRefElement; pageNav?: NavModelItem; sectionNav?: NavModel; } @@ -234,11 +235,9 @@ export class UnthemedDashboardPage extends PureComponent { locationService.partial({ editPanel: null, viewPanel: null }); } - if (config.featureToggles.bodyScrolling) { - // Update window scroll position - if (this.state.updateScrollTop !== undefined && this.state.updateScrollTop !== prevState.updateScrollTop) { - window.scrollTo(0, this.state.updateScrollTop); - } + // Update window scroll position + if (this.state.updateScrollTop !== undefined && this.state.updateScrollTop !== prevState.updateScrollTop) { + this.state.scrollElement?.scrollTo(0, this.state.updateScrollTop); } } @@ -263,19 +262,17 @@ export class UnthemedDashboardPage extends PureComponent { const updatedState = { ...state }; - if (config.featureToggles.bodyScrolling) { - // Entering settings view - if (!state.editView && urlEditView) { - updatedState.editView = urlEditView; - updatedState.rememberScrollTop = window.scrollY; - updatedState.updateScrollTop = 0; - } + // Entering settings view + if (!state.editView && urlEditView) { + updatedState.editView = urlEditView; + updatedState.rememberScrollTop = state.scrollElement?.scrollTop; + updatedState.updateScrollTop = 0; + } - // Leaving settings view - else if (state.editView && !urlEditView) { - updatedState.updateScrollTop = state.rememberScrollTop; - updatedState.editView = null; - } + // Leaving settings view + else if (state.editView && !urlEditView) { + updatedState.updateScrollTop = state.rememberScrollTop; + updatedState.editView = null; } // Entering edit mode @@ -284,12 +281,7 @@ export class UnthemedDashboardPage extends PureComponent { if (panel) { if (dashboard.canEditPanel(panel)) { updatedState.editPanel = panel; - updatedState.rememberScrollTop = config.featureToggles.bodyScrolling - ? window.scrollY - : state.scrollElement?.scrollTop; - if (config.featureToggles.bodyScrolling) { - updatedState.updateScrollTop = 0; - } + updatedState.rememberScrollTop = state.scrollElement?.scrollTop; } else { updatedState.editPanelAccessDenied = true; } @@ -311,9 +303,7 @@ export class UnthemedDashboardPage extends PureComponent { // Should move this state out of dashboard in the future dashboard.initViewPanel(panel); updatedState.viewPanel = panel; - updatedState.rememberScrollTop = config.featureToggles.bodyScrolling - ? window.scrollY - : state.scrollElement?.scrollTop; + updatedState.rememberScrollTop = state.scrollElement?.scrollTop; updatedState.updateScrollTop = 0; } else { updatedState.panelNotFound = true; @@ -337,7 +327,7 @@ export class UnthemedDashboardPage extends PureComponent { return updateStatePageNavFromProps(props, updatedState); } - setScrollRef = (scrollElement: HTMLDivElement): void => { + setScrollRef = (scrollElement: ScrollRefElement): void => { this.setState({ scrollElement }); }; @@ -366,7 +356,7 @@ export class UnthemedDashboardPage extends PureComponent { render() { const { dashboard, initError, queryParams, theme } = this.props; - const { editPanel, viewPanel, updateScrollTop, pageNav, sectionNav } = this.state; + const { editPanel, viewPanel, pageNav, sectionNav } = this.state; const kioskMode = getKioskMode(this.props.queryParams); const styles = getStyles(theme); @@ -443,8 +433,7 @@ export class UnthemedDashboardPage extends PureComponent { pageNav={pageNav} layout={PageLayoutType.Canvas} className={pageClassName} - scrollRef={this.setScrollRef} - scrollTop={updateScrollTop} + onSetScrollRef={this.setScrollRef} > {showToolbar && (
From 21bf013a8eb601d98bb52d413fee3ea9eb899b75 Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Wed, 21 Aug 2024 16:11:55 +0200 Subject: [PATCH 197/229] Add support for synchronous plugin installation (#92129) --- pkg/api/frontendsettings.go | 2 +- pkg/api/plugins.go | 4 +- pkg/api/plugins_test.go | 2 +- .../plugininstaller/service.go | 53 ++++++++++-- .../plugininstaller/service_test.go | 86 +++++++++++++++---- pkg/setting/setting.go | 3 +- pkg/setting/setting_plugins.go | 5 +- 7 files changed, 125 insertions(+), 30 deletions(-) diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index c8e1170454c..9bf8c35ef75 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -274,7 +274,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro PluginAdminExternalManageEnabled: hs.Cfg.PluginAdminEnabled && hs.Cfg.PluginAdminExternalManageEnabled, PluginCatalogHiddenPlugins: hs.Cfg.PluginCatalogHiddenPlugins, PluginCatalogManagedPlugins: hs.managedPluginsService.ManagedPlugins(c.Req.Context()), - PluginCatalogPreinstalledPlugins: hs.Cfg.InstallPlugins, + PluginCatalogPreinstalledPlugins: hs.Cfg.PreinstallPlugins, ExpressionsEnabled: hs.Cfg.ExpressionsEnabled, AwsAllowedAuthProviders: hs.Cfg.AWSAllowedAuthProviders, AwsAssumeRoleEnabled: hs.Cfg.AWSAssumeRoleEnabled, diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 6b25d977ff1..0bbb49b7b47 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -458,7 +458,7 @@ func (hs *HTTPServer) InstallPlugin(c *contextmodel.ReqContext) response.Respons hs.log.Info("Plugin install/update requested", "pluginId", pluginID, "user", c.Login) - for _, preinstalled := range hs.Cfg.InstallPlugins { + for _, preinstalled := range hs.Cfg.PreinstallPlugins { if preinstalled.ID == pluginID && preinstalled.Version != "" { return response.Error(http.StatusConflict, "Cannot update a pinned pre-installed plugin", nil) } @@ -502,7 +502,7 @@ func (hs *HTTPServer) UninstallPlugin(c *contextmodel.ReqContext) response.Respo return response.Error(http.StatusNotFound, "Plugin not installed", nil) } - for _, preinstalled := range hs.Cfg.InstallPlugins { + for _, preinstalled := range hs.Cfg.PreinstallPlugins { if preinstalled.ID == pluginID { return response.Error(http.StatusConflict, "Cannot uninstall a pre-installed plugin", nil) } diff --git a/pkg/api/plugins_test.go b/pkg/api/plugins_test.go index d1d44affb75..fd1ad0df80f 100644 --- a/pkg/api/plugins_test.go +++ b/pkg/api/plugins_test.go @@ -95,7 +95,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) { hs.Cfg.PluginAdminEnabled = tc.pluginAdminEnabled hs.Cfg.PluginAdminExternalManageEnabled = tc.pluginAdminExternalManageEnabled hs.Cfg.RBAC.SingleOrganization = tc.singleOrganization - hs.Cfg.InstallPlugins = []setting.InstallPlugin{{ID: "grafana-preinstalled-datasource", Version: "1.0.0"}} + hs.Cfg.PreinstallPlugins = []setting.InstallPlugin{{ID: "grafana-preinstalled-datasource", Version: "1.0.0"}} hs.orgService = &orgtest.FakeOrgService{ExpectedOrg: &org.Org{}} hs.accesscontrolService = &actest.FakeService{} diff --git a/pkg/services/pluginsintegration/plugininstaller/service.go b/pkg/services/pluginsintegration/plugininstaller/service.go index 76f993f2b28..a19d082fac7 100644 --- a/pkg/services/pluginsintegration/plugininstaller/service.go +++ b/pkg/services/pluginsintegration/plugininstaller/service.go @@ -3,8 +3,10 @@ package plugininstaller import ( "context" "errors" + "fmt" "runtime" + "cuelang.org/go/pkg/time" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -18,29 +20,57 @@ type Service struct { log log.Logger pluginInstaller plugins.Installer pluginStore pluginstore.Store + failOnErr bool } -func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, pluginStore pluginstore.Store, pluginInstaller plugins.Installer) *Service { +func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, pluginStore pluginstore.Store, pluginInstaller plugins.Installer) (*Service, error) { s := &Service{ features: features, log: log.New("plugin.backgroundinstaller"), cfg: cfg, pluginInstaller: pluginInstaller, pluginStore: pluginStore, + failOnErr: !cfg.PreinstallPluginsAsync, // Fail on error if preinstall is synchronous } - return s + if !cfg.PreinstallPluginsAsync { + // Block initialization process until plugins are installed + err := s.installPluginsWithTimeout() + if err != nil { + return nil, err + } + } + return s, nil } // IsDisabled disables background installation of plugins. func (s *Service) IsDisabled() bool { return !s.features.IsEnabled(context.Background(), featuremgmt.FlagBackgroundPluginInstaller) || - len(s.cfg.InstallPlugins) == 0 + len(s.cfg.PreinstallPlugins) == 0 || + !s.cfg.PreinstallPluginsAsync } -func (s *Service) Run(ctx context.Context) error { +func (s *Service) installPluginsWithTimeout() error { + // Installation process does not timeout by default nor reuses the context + // passed to the request so we need to handle the timeout here. + // We could make this timeout configurable in the future. + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute) + defer cancel() + done := make(chan struct{ err error }) + go func() { + done <- struct{ err error }{err: s.installPlugins(ctx)} + }() + select { + case <-ctx.Done(): + return fmt.Errorf("failed to install plugins: %w", ctx.Err()) + case d := <-done: + return d.err + } +} + +func (s *Service) installPlugins(ctx context.Context) error { compatOpts := plugins.NewCompatOpts(s.cfg.BuildVersion, runtime.GOOS, runtime.GOARCH) - for _, installPlugin := range s.cfg.InstallPlugins { + for _, installPlugin := range s.cfg.PreinstallPlugins { // Check if the plugin is already installed p, exists := s.pluginStore.Plugin(ctx, installPlugin.ID) if exists { @@ -59,6 +89,10 @@ func (s *Service) Run(ctx context.Context) error { s.log.Debug("Plugin already installed", "pluginId", installPlugin.ID, "version", installPlugin.Version) continue } + if s.failOnErr { + // Halt execution in the synchronous scenario + return fmt.Errorf("failed to install plugin %s@%s: %w", installPlugin.ID, installPlugin.Version, err) + } s.log.Error("Failed to install plugin", "pluginId", installPlugin.ID, "version", installPlugin.Version, "error", err) continue } @@ -67,3 +101,12 @@ func (s *Service) Run(ctx context.Context) error { return nil } + +func (s *Service) Run(ctx context.Context) error { + err := s.installPlugins(ctx) + if err != nil { + // Unexpected error, asynchronous installation should not return errors + s.log.Error("Failed to install plugins", "error", err) + } + return nil +} diff --git a/pkg/services/pluginsintegration/plugininstaller/service_test.go b/pkg/services/pluginsintegration/plugininstaller/service_test.go index 19cc3386c6b..59feffe22de 100644 --- a/pkg/services/pluginsintegration/plugininstaller/service_test.go +++ b/pkg/services/pluginsintegration/plugininstaller/service_test.go @@ -16,14 +16,16 @@ import ( // Test if the service is disabled func TestService_IsDisabled(t *testing.T) { // Create a new service - s := ProvideService( + s, err := ProvideService( &setting.Cfg{ - InstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}}, + PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}}, + PreinstallPluginsAsync: true, }, featuremgmt.WithFeatures(featuremgmt.FlagBackgroundPluginInstaller), pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}), &fakes.FakePluginInstaller{}, ) + require.NoError(t, err) // Check if the service is disabled if s.IsDisabled() { @@ -34,9 +36,9 @@ func TestService_IsDisabled(t *testing.T) { func TestService_Run(t *testing.T) { t.Run("Installs a plugin", func(t *testing.T) { installed := false - s := ProvideService( + s, err := ProvideService( &setting.Cfg{ - InstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}}, + PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}}, }, featuremgmt.WithFeatures(), pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}), @@ -47,17 +49,19 @@ func TestService_Run(t *testing.T) { }, }, ) + require.NoError(t, err) - err := s.Run(context.Background()) + err = s.Run(context.Background()) require.NoError(t, err) require.True(t, installed) }) t.Run("Install a plugin with version", func(t *testing.T) { installed := false - s := ProvideService( + s, err := ProvideService( &setting.Cfg{ - InstallPlugins: []setting.InstallPlugin{{ID: "myplugin", Version: "1.0.0"}}, + PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin", Version: "1.0.0"}}, + PreinstallPluginsAsync: true, }, featuremgmt.WithFeatures(), pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}), @@ -70,8 +74,9 @@ func TestService_Run(t *testing.T) { }, }, ) + require.NoError(t, err) - err := s.Run(context.Background()) + err = s.Run(context.Background()) require.NoError(t, err) require.True(t, installed) }) @@ -84,9 +89,10 @@ func TestService_Run(t *testing.T) { }, }) require.NoError(t, err) - s := ProvideService( + s, err := ProvideService( &setting.Cfg{ - InstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}}, + PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}}, + PreinstallPluginsAsync: true, }, featuremgmt.WithFeatures(), pluginstore.New(preg, &fakes.FakeLoader{}), @@ -97,6 +103,7 @@ func TestService_Run(t *testing.T) { }, }, ) + require.NoError(t, err) err = s.Run(context.Background()) require.NoError(t, err) @@ -114,9 +121,10 @@ func TestService_Run(t *testing.T) { }, }) require.NoError(t, err) - s := ProvideService( + s, err := ProvideService( &setting.Cfg{ - InstallPlugins: []setting.InstallPlugin{{ID: "myplugin", Version: "2.0.0"}}, + PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin", Version: "2.0.0"}}, + PreinstallPluginsAsync: true, }, featuremgmt.WithFeatures(), pluginstore.New(preg, &fakes.FakeLoader{}), @@ -127,6 +135,7 @@ func TestService_Run(t *testing.T) { }, }, ) + require.NoError(t, err) err = s.Run(context.Background()) require.NoError(t, err) @@ -135,9 +144,10 @@ func TestService_Run(t *testing.T) { t.Run("Install multiple plugins", func(t *testing.T) { installed := 0 - s := ProvideService( + s, err := ProvideService( &setting.Cfg{ - InstallPlugins: []setting.InstallPlugin{{ID: "myplugin1"}, {ID: "myplugin2"}}, + PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin1"}, {ID: "myplugin2"}}, + PreinstallPluginsAsync: true, }, featuremgmt.WithFeatures(), pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}), @@ -148,17 +158,19 @@ func TestService_Run(t *testing.T) { }, }, ) + require.NoError(t, err) - err := s.Run(context.Background()) + err = s.Run(context.Background()) require.NoError(t, err) require.Equal(t, 2, installed) }) t.Run("Fails to install a plugin but install the rest", func(t *testing.T) { installed := 0 - s := ProvideService( + s, err := ProvideService( &setting.Cfg{ - InstallPlugins: []setting.InstallPlugin{{ID: "myplugin1"}, {ID: "myplugin2"}}, + PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin1"}, {ID: "myplugin2"}}, + PreinstallPluginsAsync: true, }, featuremgmt.WithFeatures(), pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}), @@ -172,8 +184,46 @@ func TestService_Run(t *testing.T) { }, }, ) - err := s.Run(context.Background()) + require.NoError(t, err) + err = s.Run(context.Background()) require.NoError(t, err) require.Equal(t, 1, installed) }) + + t.Run("Install a blocking plugin", func(t *testing.T) { + installed := false + _, err := ProvideService( + &setting.Cfg{ + PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}}, + PreinstallPluginsAsync: false, + }, + featuremgmt.WithFeatures(), + pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}), + &fakes.FakePluginInstaller{ + AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.CompatOpts) error { + installed = true + return nil + }, + }, + ) + require.NoError(t, err) + require.True(t, installed) + }) + + t.Run("Fails to install a blocking plugin", func(t *testing.T) { + _, err := ProvideService( + &setting.Cfg{ + PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}}, + PreinstallPluginsAsync: false, + }, + featuremgmt.WithFeatures(), + pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}), + &fakes.FakePluginInstaller{ + AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.CompatOpts) error { + return plugins.NotFoundError{} + }, + }, + ) + require.ErrorAs(t, err, &plugins.NotFoundError{}) + }) } diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 22f34b43f92..04612d220ac 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -197,7 +197,8 @@ type Cfg struct { HideAngularDeprecation []string PluginInstallToken string ForwardHostEnvVars []string - InstallPlugins []InstallPlugin + PreinstallPlugins []InstallPlugin + PreinstallPluginsAsync bool PluginsCDNURLTemplate string PluginLogBackendRequests bool diff --git a/pkg/setting/setting_plugins.go b/pkg/setting/setting_plugins.go index d6cf55172d5..54f315eef02 100644 --- a/pkg/setting/setting_plugins.go +++ b/pkg/setting/setting_plugins.go @@ -42,7 +42,7 @@ func (cfg *Cfg) readPluginSettings(iniFile *ini.File) error { disablePreinstall := pluginsSection.Key("disable_preinstall").MustBool(false) if !disablePreinstall { rawInstallPlugins := util.SplitString(pluginsSection.Key("preinstall").MustString("")) - cfg.InstallPlugins = make([]InstallPlugin, len(rawInstallPlugins)) + cfg.PreinstallPlugins = make([]InstallPlugin, len(rawInstallPlugins)) for i, plugin := range rawInstallPlugins { parts := strings.Split(plugin, "@") id := parts[0] @@ -50,8 +50,9 @@ func (cfg *Cfg) readPluginSettings(iniFile *ini.File) error { if len(parts) == 2 { v = parts[1] } - cfg.InstallPlugins[i] = InstallPlugin{id, v} + cfg.PreinstallPlugins[i] = InstallPlugin{id, v} } + cfg.PreinstallPluginsAsync = pluginsSection.Key("preinstall_async").MustBool(true) } cfg.PluginCatalogURL = pluginsSection.Key("plugin_catalog_url").MustString("https://grafana.com/grafana/plugins/") From a04d2f44f896517dbe5648ead0737a3d545ec795 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Wed, 21 Aug 2024 10:55:16 -0400 Subject: [PATCH 198/229] chore: add spans to publicdashboards service methods (#92149) * chore: add spans to publicdashboards service methods * add tracing to test service * test fixture whackamole * move tracer to a package var * Update pkg/services/publicdashboards/service/service.go Co-authored-by: Dave Henderson --------- Co-authored-by: Dave Henderson --- .../publicdashboards/service/service.go | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/pkg/services/publicdashboards/service/service.go b/pkg/services/publicdashboards/service/service.go index 31a107f26c7..133ebe81e2f 100644 --- a/pkg/services/publicdashboards/service/service.go +++ b/pkg/services/publicdashboards/service/service.go @@ -9,6 +9,7 @@ import ( "github.com/google/uuid" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" + "go.opentelemetry.io/otel" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/infra/log" @@ -45,6 +46,7 @@ type PublicDashboardServiceImpl struct { } var LogPrefix = "publicdashboards.service" +var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/publicdashboards/service") // Gives us compile time error if the service does not adhere to the contract of // the interface @@ -79,6 +81,9 @@ func ProvideService( } func (pd *PublicDashboardServiceImpl) GetPublicDashboardForView(ctx context.Context, accessToken string) (*dtos.DashboardFullWithMeta, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.GetPublicDashboardForView") + defer span.End() + pubdash, dash, err := pd.FindEnabledPublicDashboardAndDashboardByAccessToken(ctx, accessToken) if err != nil { return nil, err @@ -110,10 +115,14 @@ func (pd *PublicDashboardServiceImpl) GetPublicDashboardForView(ctx context.Cont // FindByDashboardUid this method would be replaced by another implementation for Enterprise version func (pd *PublicDashboardServiceImpl) FindByDashboardUid(ctx context.Context, orgId int64, dashboardUid string) (*PublicDashboard, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.FindByDashboardUid") + defer span.End() return pd.serviceWrapper.FindByDashboardUid(ctx, orgId, dashboardUid) } func (pd *PublicDashboardServiceImpl) Find(ctx context.Context, uid string) (*PublicDashboard, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.Find") + defer span.End() pubdash, err := pd.store.Find(ctx, uid) if err != nil { return nil, ErrInternalServerError.Errorf("Find: failed to find public dashboard%w", err) @@ -123,6 +132,8 @@ func (pd *PublicDashboardServiceImpl) Find(ctx context.Context, uid string) (*Pu // FindDashboard Gets a dashboard by Uid func (pd *PublicDashboardServiceImpl) FindDashboard(ctx context.Context, orgId int64, dashboardUid string) (*dashboards.Dashboard, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.FindDashboard") + defer span.End() dash, err := pd.dashboardService.GetDashboard(ctx, &dashboards.GetDashboardQuery{UID: dashboardUid, OrgID: orgId}) if err != nil { var dashboardErr dashboards.DashboardErr @@ -139,6 +150,8 @@ func (pd *PublicDashboardServiceImpl) FindDashboard(ctx context.Context, orgId i // FindByAccessToken Gets public dashboard by access token func (pd *PublicDashboardServiceImpl) FindByAccessToken(ctx context.Context, accessToken string) (*PublicDashboard, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.FindByAccessToken") + defer span.End() pubdash, err := pd.store.FindByAccessToken(ctx, accessToken) if err != nil { return nil, ErrInternalServerError.Errorf("FindByAccessToken: failed to find a public dashboard: %w", err) @@ -153,6 +166,8 @@ func (pd *PublicDashboardServiceImpl) FindByAccessToken(ctx context.Context, acc // FindEnabledPublicDashboardAndDashboardByAccessToken Gets public dashboard and a dashboard by access token if public dashboard is enabled func (pd *PublicDashboardServiceImpl) FindEnabledPublicDashboardAndDashboardByAccessToken(ctx context.Context, accessToken string) (*PublicDashboard, *dashboards.Dashboard, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.FindEnabledPublicDashboardAndDashboardByAccessToken") + defer span.End() pubdash, dash, err := pd.FindPublicDashboardAndDashboardByAccessToken(ctx, accessToken) if err != nil { return pubdash, dash, err @@ -171,6 +186,8 @@ func (pd *PublicDashboardServiceImpl) FindEnabledPublicDashboardAndDashboardByAc // FindPublicDashboardAndDashboardByAccessToken Gets public dashboard and a dashboard by access token func (pd *PublicDashboardServiceImpl) FindPublicDashboardAndDashboardByAccessToken(ctx context.Context, accessToken string) (*PublicDashboard, *dashboards.Dashboard, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.FindPublicDashboardAndDashboardByAccessToken") + defer span.End() pubdash, err := pd.FindByAccessToken(ctx, accessToken) if err != nil { return nil, nil, err @@ -190,6 +207,8 @@ func (pd *PublicDashboardServiceImpl) FindPublicDashboardAndDashboardByAccessTok // Creates and validates the public dashboard and saves it to the database func (pd *PublicDashboardServiceImpl) Create(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.Create") + defer span.End() // validate fields err := validation.ValidatePublicDashboard(dto) if err != nil { @@ -247,6 +266,8 @@ func (pd *PublicDashboardServiceImpl) Create(ctx context.Context, u *user.Signed // Update: updates an existing public dashboard based on publicdashboard.Uid func (pd *PublicDashboardServiceImpl) Update(ctx context.Context, u *user.SignedInUser, dto *SavePublicDashboardDTO) (*PublicDashboard, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.Update") + defer span.End() // validate fields err := validation.ValidatePublicDashboard(dto) if err != nil { @@ -303,6 +324,8 @@ func (pd *PublicDashboardServiceImpl) Update(ctx context.Context, u *user.Signed // NewPublicDashboardUid Generates a unique uid to create a public dashboard. Will make 3 attempts and fail if it cannot find an unused uid func (pd *PublicDashboardServiceImpl) NewPublicDashboardUid(ctx context.Context) (string, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.NewPublicDashboardUid") + defer span.End() var uid string for i := 0; i < 3; i++ { uid = util.GenerateShortUID() @@ -317,6 +340,8 @@ func (pd *PublicDashboardServiceImpl) NewPublicDashboardUid(ctx context.Context) // NewPublicDashboardAccessToken Generates a unique accessToken to create a public dashboard. Will make 3 attempts and fail if it cannot find an unused access token func (pd *PublicDashboardServiceImpl) NewPublicDashboardAccessToken(ctx context.Context) (string, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.NewPublicDashboardAccessToken") + defer span.End() var accessToken string for i := 0; i < 3; i++ { var err error @@ -335,6 +360,8 @@ func (pd *PublicDashboardServiceImpl) NewPublicDashboardAccessToken(ctx context. // FindAllWithPagination Returns a list of public dashboards by orgId, based on permissions and with pagination func (pd *PublicDashboardServiceImpl) FindAllWithPagination(ctx context.Context, query *PublicDashboardListQuery) (*PublicDashboardListResponseWithPagination, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.FindAllWithPagination") + defer span.End() query.Offset = query.Limit * (query.Page - 1) resp, err := pd.store.FindAllWithPagination(ctx, query) if err != nil { @@ -348,18 +375,26 @@ func (pd *PublicDashboardServiceImpl) FindAllWithPagination(ctx context.Context, } func (pd *PublicDashboardServiceImpl) ExistsEnabledByDashboardUid(ctx context.Context, dashboardUid string) (bool, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.ExistsEnabledByDashboardUid") + defer span.End() return pd.store.ExistsEnabledByDashboardUid(ctx, dashboardUid) } func (pd *PublicDashboardServiceImpl) ExistsEnabledByAccessToken(ctx context.Context, accessToken string) (bool, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.ExistsEnabledByAccessToken") + defer span.End() return pd.store.ExistsEnabledByAccessToken(ctx, accessToken) } func (pd *PublicDashboardServiceImpl) GetOrgIdByAccessToken(ctx context.Context, accessToken string) (int64, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.GetOrgIdByAccessToken") + defer span.End() return pd.store.GetOrgIdByAccessToken(ctx, accessToken) } func (pd *PublicDashboardServiceImpl) Delete(ctx context.Context, uid string, dashboardUid string) error { + ctx, span := tracer.Start(ctx, "publicdashboards.Delete") + defer span.End() // get existing public dashboard if exists existingPubdash, err := pd.store.Find(ctx, uid) if err != nil { @@ -377,6 +412,8 @@ func (pd *PublicDashboardServiceImpl) Delete(ctx context.Context, uid string, da } func (pd *PublicDashboardServiceImpl) DeleteByDashboard(ctx context.Context, dashboard *dashboards.Dashboard) error { + ctx, span := tracer.Start(ctx, "publicdashboards.DeleteByDashboard") + defer span.End() if dashboard.IsFolder { // get all pubdashes for the folder pubdashes, err := pd.store.FindByFolder(ctx, dashboard.OrgID, dashboard.UID) @@ -464,6 +501,8 @@ func GenerateAccessToken() (string, error) { } func (pd *PublicDashboardServiceImpl) newCreatePublicDashboard(ctx context.Context, dto *SavePublicDashboardDTO) (*PublicDashboard, error) { + ctx, span := tracer.Start(ctx, "publicdashboards.newCreatePublicDashboard") + defer span.End() //Check if uid already exists, if none then auto generate var err error uid := dto.PublicDashboard.Uid From e6754555028aaeb5c8c1ab5d5049e3b42ce3a65e Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 21 Aug 2024 16:16:12 +0100 Subject: [PATCH 199/229] PublicDashboards: Fix public dashboards when `publicDashboardsScene` is enabled (#92187) * fix public dashboards pages when publicDashboardsScene is enabled * properly handle react-grid-layout at small screen sizes * use unset instead of auto --- packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts | 3 +++ .../dashboard-scene/pages/PublicDashboardScenePage.tsx | 2 ++ 2 files changed, 5 insertions(+) diff --git a/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts b/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts index bbd6d6da876..6fd77b0e77e 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts +++ b/packages/grafana-ui/src/themes/GlobalStyles/dashboardGrid.ts @@ -20,6 +20,9 @@ export function getDashboardGridStyles(theme: GrafanaTheme2) { }, [theme.breakpoints.down('md')]: { + '.react-grid-layout': { + height: 'unset !important', + }, '.react-grid-item': { display: 'block !important', transitionProperty: 'none !important', diff --git a/public/app/features/dashboard-scene/pages/PublicDashboardScenePage.tsx b/public/app/features/dashboard-scene/pages/PublicDashboardScenePage.tsx index 4be33dc4365..ddf886eeee6 100644 --- a/public/app/features/dashboard-scene/pages/PublicDashboardScenePage.tsx +++ b/public/app/features/dashboard-scene/pages/PublicDashboardScenePage.tsx @@ -147,7 +147,9 @@ function getStyles(theme: GrafanaTheme2) { }), body: css({ label: 'body', + display: 'flex', flex: 1, + flexDirection: 'column', overflowY: 'auto', }), }; From 02c820382d65a493f4a95076b5d2df104b8de9a8 Mon Sep 17 00:00:00 2001 From: brendamuir <100768211+brendamuir@users.noreply.github.com> Date: Wed, 21 Aug 2024 17:20:59 +0200 Subject: [PATCH 200/229] Alerting docs: separates doc on create alerts from panels (#92131) * Alerting docs: separates doc on create alerts from panels * deletes previous panel section * adds time series limitation and play link * removes play from old topic * ran prettier * removed play shortcode, typo * adds feedback from gilles --------- Co-authored-by: tonypowa --- .../alerting-rules/create-alerts-panels.md | 45 +++++++++++++++++++ .../create-grafana-managed-rule.md | 16 +------ .../create-mimir-loki-managed-rule.md | 4 +- 3 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 docs/sources/alerting/alerting-rules/create-alerts-panels.md diff --git a/docs/sources/alerting/alerting-rules/create-alerts-panels.md b/docs/sources/alerting/alerting-rules/create-alerts-panels.md new file mode 100644 index 00000000000..290936fd7ea --- /dev/null +++ b/docs/sources/alerting/alerting-rules/create-alerts-panels.md @@ -0,0 +1,45 @@ +--- +canonical: https://grafana.com/docs/grafana/latest/alerting/alerting-rules/create-alerts-panels/ +description: Create alert rules from panels. Reuse the queries in the panel and create alert rules based on them. +keywords: + - grafana + - alerting + - panels + - create + - grafana-managed + - data source-managed +labels: + products: + - cloud + - enterprise + - oss +title: Create alert rules from panels +weight: 400 +--- + +## Create alert rules from panels + +Create alert rules from time series panels. By doing so, you can reuse the queries in the panel and create alert rules based on them. + +1. Navigate to a dashboard in the **Dashboards** section. +2. Hover over the top-right corner of a time series panel and click the panel menu icon. +3. From the dropdown menu, select **More...** > **New alert rule**. + +The New alert rule form opens where you can configure and create your alert rule based on the query used in the panel. + +{{% admonition type="note" %}} +Changes to the panel aren't reflected on the linked alert rules. If you change a query, you have to update it in both the panel and the alert rule. + +Alert rules are only supported in [time series](ref:time-series) visualizations. +{{% /admonition %}} + +{{< docs/play title="visualizations with linked alerts in Grafana" url="https://play.grafana.org/d/000000074/" >}} + +## View alert rules from panels + +To view alert rules associated with a time series panel, complete the following steps. + +1. Open the panel editor by hovering over the top-right corner of any panel +1. Click the panel menu icon that appears. +1. Click **Edit**. +1. Click the **Alert** tab to view existing alert rules or create a new one. diff --git a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md index e0407e2062c..66d2e30c660 100644 --- a/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md +++ b/docs/sources/alerting/alerting-rules/create-grafana-managed-rule.md @@ -232,14 +232,12 @@ Annotations add metadata to provide more information on the alert in your alert 1. Optional: Add a custom annotation 1. Optional: Add a **dashboard and panel link**. - Links alerts to panels in a dashboard. + Links alert rules to panels in a dashboard. {{% admonition type="note" %}} - At the moment, alerts are only supported in the [time series](ref:time-series) and [alert list](ref:alert-list) visualizations. + At the moment, alert rules are only supported in [time series](ref:time-series) and [alert list](ref:alert-list) visualizations. {{% /admonition %}} - {{< docs/play title="visualizations with linked alerts in Grafana" url="https://play.grafana.org/d/000000074/" >}} - 1. Click **Save rule**. ## Configure no data and error handling @@ -267,13 +265,3 @@ You can also configure the alert instance state when its evaluation returns an e | Keep Last State | Maintains the alert instance in its last state. Useful for mitigating temporary issues, refer to [Keep last state](ref:keep-last-state). | When you configure the No data or Error behavior to `Alerting` or `Normal`, Grafana will attempt to keep a stable set of fields under notification `Values`. If your query returns no data or an error, Grafana re-uses the latest known set of fields in `Values`, but will use `-1` in place of the measured value. - -## Create alerts from panels - -Create alerts from any panel type. This means you can reuse the queries in the panel and create alerts based on them. - -1. Navigate to a dashboard in the **Dashboards** section. -2. In the top right corner of the panel, click on the three dots (ellipses). -3. From the dropdown menu, select **More...** and then choose **New alert rule**. - -This will open the alert rule form, allowing you to configure and create your alert based on the current panel's query. diff --git a/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-rule.md b/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-rule.md index c21dc2e9fa7..a213449754f 100644 --- a/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-rule.md +++ b/docs/sources/alerting/alerting-rules/create-mimir-loki-managed-rule.md @@ -145,9 +145,7 @@ Annotations add metadata to provide more information on the alert in your alert Links alerts to panels in a dashboard. {{% admonition type="note" %}} - At the moment, alerts are only supported in the [time series](ref:time-series) and [alert list](ref:alert-list) visualizations. + At the moment, alert rules are only supported in [time series](ref:time-series) and [alert list](ref:alert-list) visualizations. {{% /admonition %}} - {{< docs/play title="visualizations with linked alerts in Grafana" url="https://play.grafana.org/d/000000074/" >}} - 1. Click **Save rule**. From df3d8915ba7116cef8be93d41365e4cfefa45f4a Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Wed, 21 Aug 2024 11:40:42 -0400 Subject: [PATCH 201/229] Chore: Bump Go to 1.23.0 (#92105) * chore: Bump Go to 1.23.0 Signed-off-by: Dave Henderson * update swagger files Signed-off-by: Dave Henderson * chore: update .bingo/README.md formatting to satisfy prettier Signed-off-by: Dave Henderson * chore(lint): Fix new lint errors found by golangci-lint 1.60.1 and Go 1.23 Signed-off-by: Dave Henderson * keep golden file * update openapi * add name to expected output * chore(lint): rearrange imports to a sensible order Signed-off-by: Dave Henderson --------- Signed-off-by: Dave Henderson Co-authored-by: Ryan McKinley --- .bingo/Variables.mk | 6 +- .bingo/golangci-lint.mod | 6 +- .bingo/golangci-lint.sum | 45 +++ .bingo/variables.env | 2 +- .drone.yml | 246 ++++++------- .github/workflows/go_lint.yml | 4 +- Dockerfile | 2 +- Makefile | 2 +- go.mod | 2 +- go.work | 2 +- go.work.sum | 26 -- hack/go.mod | 2 +- pkg/aggregator/go.mod | 2 +- pkg/aggregator/go.sum | 341 ------------------ pkg/api/frontend_logging.go | 6 +- pkg/apimachinery/go.mod | 2 +- pkg/apiserver/go.mod | 2 +- pkg/build/go.mod | 2 +- pkg/build/wire/go.mod | 2 +- .../UnexportedStruct/want/wire_errs.txt | 2 +- .../commands/conflict_user_command.go | 18 +- pkg/expr/hysteresis.go | 2 +- pkg/infra/filestorage/test_utils.go | 4 +- pkg/infra/filestorage/wrapper_test.go | 3 +- pkg/login/social/connectors/generic_oauth.go | 10 +- pkg/login/social/connectors/github_oauth.go | 6 +- pkg/promlib/go.mod | 2 +- pkg/promlib/querydata/request.go | 6 +- pkg/registry/apis/datasource/sub_proxy.go | 2 +- pkg/registry/apis/query/client/plugin.go | 2 +- pkg/registry/apis/query/query.go | 2 +- pkg/semconv/go.mod | 2 +- pkg/services/accesscontrol/models.go | 2 +- .../annotationsimpl/composite_store.go | 7 +- pkg/services/authz/zanzana/client/client.go | 7 +- pkg/services/featuremgmt/toggles_gen_test.go | 3 +- pkg/services/navtree/navtreeimpl/navtree.go | 2 +- pkg/services/ngalert/accesscontrol/models.go | 6 +- pkg/services/ngalert/api/util.go | 5 +- .../provisioning/plugins/config_reader.go | 13 +- pkg/services/query/query.go | 3 +- pkg/services/searchusers/searchusers.go | 10 +- pkg/services/sqlstore/replstore.go | 2 +- pkg/services/store/config.go | 4 +- pkg/services/store/service.go | 2 + pkg/storage/unified/resource/go.mod | 2 +- .../sql/sqltemplate/mocks/test_snapshots.go | 5 +- pkg/tsdb/cloudwatch/log_actions.go | 6 +- pkg/tsdb/cloudwatch/models/settings.go | 2 +- .../sqleng/sql_engine.go | 7 +- .../sims/engine.go | 2 +- pkg/tsdb/influxdb/flux/executor.go | 6 +- pkg/tsdb/influxdb/fsql/arrow_test.go | 2 +- .../influxql/buffered/response_parser.go | 5 +- .../influxdb/influxql/converter/converter.go | 7 +- pkg/tsdb/influxdb/influxql/influxql.go | 2 +- pkg/tsdb/mssql/sqleng/sql_engine.go | 7 +- pkg/tsdb/mysql/sqleng/sql_engine.go | 7 +- public/api-enterprise-spec.json | 11 +- public/api-merged.json | 6 +- public/openapi3.json | 6 +- scripts/drone/variables.star | 2 +- scripts/go-workspace/go.mod | 2 +- 63 files changed, 289 insertions(+), 627 deletions(-) diff --git a/.bingo/Variables.mk b/.bingo/Variables.mk index cfcedc5a4b9..21d5bcb5c62 100644 --- a/.bingo/Variables.mk +++ b/.bingo/Variables.mk @@ -35,11 +35,11 @@ $(DRONE): $(BINGO_DIR)/drone.mod @echo "(re)installing $(GOBIN)/drone-v1.5.0" @cd $(BINGO_DIR) && GOWORK=off CGO_ENABLED=0 $(GO) build -mod=mod -modfile=drone.mod -o=$(GOBIN)/drone-v1.5.0 "github.com/drone/drone-cli/drone" -GOLANGCI_LINT := $(GOBIN)/golangci-lint-v1.59.1 +GOLANGCI_LINT := $(GOBIN)/golangci-lint-v1.60.1 $(GOLANGCI_LINT): $(BINGO_DIR)/golangci-lint.mod @# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies. - @echo "(re)installing $(GOBIN)/golangci-lint-v1.59.1" - @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v1.59.1 "github.com/golangci/golangci-lint/cmd/golangci-lint" + @echo "(re)installing $(GOBIN)/golangci-lint-v1.60.1" + @cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=golangci-lint.mod -o=$(GOBIN)/golangci-lint-v1.60.1 "github.com/golangci/golangci-lint/cmd/golangci-lint" JB := $(GOBIN)/jb-v0.5.1 $(JB): $(BINGO_DIR)/jb.mod diff --git a/.bingo/golangci-lint.mod b/.bingo/golangci-lint.mod index d92f68a1602..6f043fadfa9 100644 --- a/.bingo/golangci-lint.mod +++ b/.bingo/golangci-lint.mod @@ -1,7 +1,7 @@ module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT -go 1.22 +go 1.22.1 -toolchain go1.22.4 +toolchain go1.23.0 -require github.com/golangci/golangci-lint v1.59.1 // cmd/golangci-lint +require github.com/golangci/golangci-lint v1.60.1 // cmd/golangci-lint diff --git a/.bingo/golangci-lint.sum b/.bingo/golangci-lint.sum index abbf63506dd..60995023a48 100644 --- a/.bingo/golangci-lint.sum +++ b/.bingo/golangci-lint.sum @@ -55,18 +55,26 @@ github.com/Antonboom/testifylint v1.3.0 h1:UiqrddKs1W3YK8R0TUuWwrVKlVAnS07DTUVWW github.com/Antonboom/testifylint v1.3.0/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM= github.com/Antonboom/testifylint v1.3.1 h1:Uam4q1Q+2b6H7gvk9RQFw6jyVDdpzIirFOOrbs14eG4= github.com/Antonboom/testifylint v1.3.1/go.mod h1:NV0hTlteCkViPW9mSR4wEMfwp+Hs1T3dY60bkvSfhpM= +github.com/Antonboom/testifylint v1.4.3 h1:ohMt6AHuHgttaQ1xb6SSnxCeK4/rnK7KKzbvs7DmEck= +github.com/Antonboom/testifylint v1.4.3/go.mod h1:+8Q9+AOLsz5ZiQiiYujJKs9mNz398+M6UgslP4qgJLA= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= +github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Crocmagnon/fatcontext v0.2.2 h1:OrFlsDdOj9hW/oBEJBNSuH7QWf+E9WPVHw+x52bXVbk= github.com/Crocmagnon/fatcontext v0.2.2/go.mod h1:WSn/c/+MMNiD8Pri0ahRj0o9jVpeowzavOQplBJw6u0= +github.com/Crocmagnon/fatcontext v0.4.0 h1:4ykozu23YHA0JB6+thiuEv7iT6xq995qS1vcuWZq0tg= +github.com/Crocmagnon/fatcontext v0.4.0/go.mod h1:ZtWrXkgyfsYPzS6K3O88va6t2GEglG93vnII/F94WC0= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0 h1:sATXp1x6/axKxz2Gjxv8MALP0bXaNRfQinEwyfMcx8c= github.com/GaijinEntertainment/go-exhaustruct/v3 v3.2.0/go.mod h1:Nl76DrGNJTA1KJ0LePKBw/vznBX1EHbAZX8mwjR82nI= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0 h1:/fTUt5vmbkAcMBt4YQiuC23cV0kEsN1MVMNqeOW43cU= +github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.0/go.mod h1:ONJg5sxcbsdQQ4pOW8TGdTidT2TMAUy/2Xhr8mrYaao= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= @@ -100,6 +108,8 @@ github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= github.com/bombsimon/wsl/v4 v4.2.1 h1:Cxg6u+XDWff75SIFFmNsqnIOgob+Q9hG6y/ioKbRFiM= github.com/bombsimon/wsl/v4 v4.2.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= +github.com/bombsimon/wsl/v4 v4.4.1 h1:jfUaCkN+aUpobrMO24zwyAMwMAV5eSziCkOKEauOLdw= +github.com/bombsimon/wsl/v4 v4.4.1/go.mod h1:Xu/kDxGZTofQcDGCtQe9KCzhHphIe0fDuyWTxER9Feo= github.com/breml/bidichk v0.2.7 h1:dAkKQPLl/Qrk7hnP6P+E0xOodrq8Us7+U0o4UBOAlQY= github.com/breml/bidichk v0.2.7/go.mod h1:YodjipAGI9fGcYM7II6wFvGhdMYsC5pHDlGzqvEW3tQ= github.com/breml/errchkjson v0.3.6 h1:VLhVkqSBH96AvXEyclMR37rZslRrY2kcyq+31HCsVrA= @@ -132,6 +142,7 @@ github.com/ckaznocha/intrange v0.1.2/go.mod h1:RWffCw/vKBwHeOEwWdCikAtY0q4gGt8Vh github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= github.com/daixiang0/gci v0.12.3 h1:yOZI7VAxAGPQmkb1eqt5g/11SUlwoat1fSblGLmdiQc= @@ -208,6 +219,8 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -248,6 +261,8 @@ github.com/golangci/golangci-lint v1.59.0 h1:st69YDnAH/v2QXDcgUaZ0seQajHScPALBVk github.com/golangci/golangci-lint v1.59.0/go.mod h1:QNA32UWdUdHXnu+Ap5/ZU4WVwyp2tL94UxEXrSErjg0= github.com/golangci/golangci-lint v1.59.1 h1:CRRLu1JbhK5avLABFJ/OHVSQ0Ie5c4ulsOId1h3TTks= github.com/golangci/golangci-lint v1.59.1/go.mod h1:jX5Oif4C7P0j9++YB2MMJmoNrb01NJ8ITqKWNLewThg= +github.com/golangci/golangci-lint v1.60.1 h1:DRKNqNTQRLBJZ1il5u4fvgLQCjQc7QFs0DbhksJtVJE= +github.com/golangci/golangci-lint v1.60.1/go.mod h1:jDIPN1rYaIA+ijp9OZcUmUCoQOtZ76pOlFbi15FlLJY= github.com/golangci/misspell v0.4.1 h1:+y73iSicVy2PqyX7kmUefHusENlrP9YwuHZHPLGQj/g= github.com/golangci/misspell v0.4.1/go.mod h1:9mAN1quEo3DlpbaIKKyEvRxK1pwqR9s/Sea1bJCtlNI= github.com/golangci/misspell v0.5.1 h1:/SjR1clj5uDjNLwYzCahHwIOPmQgoH04AyQIiWGbhCM= @@ -331,6 +346,8 @@ github.com/jjti/go-spancheck v0.5.3 h1:vfq4s2IB8T3HvbpiwDTYgVPj1Ze/ZSXrTtaZRTc7C github.com/jjti/go-spancheck v0.5.3/go.mod h1:eQdOX1k3T+nAKvZDyLC3Eby0La4dZ+I19iOl5NzSPFE= github.com/jjti/go-spancheck v0.6.1 h1:ZK/wE5Kyi1VX3PJpUO2oEgeoI4FWOUm7Shb2Gbv5obI= github.com/jjti/go-spancheck v0.6.1/go.mod h1:vF1QkOO159prdo6mHRxak2CpzDpHAfKiPUDP/NeRnX8= +github.com/jjti/go-spancheck v0.6.2 h1:iYtoxqPMzHUPp7St+5yA8+cONdyXD3ug6KK15n7Pklk= +github.com/jjti/go-spancheck v0.6.2/go.mod h1:+X7lvIrR5ZdUTkxFYqzJ0abr8Sb5LOo80uOhWNqIrYA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -401,6 +418,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0j github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mgechev/revive v1.3.7 h1:502QY0vQGe9KtYJ9FpxMz9rL+Fc/P13CI5POL4uHCcE= github.com/mgechev/revive v1.3.7/go.mod h1:RJ16jUbF0OWC3co/+XTxmFNgEpUPwnnA0BRllX2aDNA= +github.com/mgechev/revive v1.3.9 h1:18Y3R4a2USSBF+QZKFQwVkBROUda7uoBlkEuBD+YD1A= +github.com/mgechev/revive v1.3.9/go.mod h1:+uxEIr5UH0TjXWHTno3xh4u7eg6jDpXKzQccA9UGhHU= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -412,6 +431,8 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/moricho/tparallel v0.3.1 h1:fQKD4U1wRMAYNngDonW5XupoB/ZGJHdpzrWqgyg9krA= github.com/moricho/tparallel v0.3.1/go.mod h1:leENX2cUv7Sv2qDgdi0D0fCftN8fRC67Bcn8pqzeYNI= +github.com/moricho/tparallel v0.3.2 h1:odr8aZVFA3NZrNybggMkYO3rgPRcqjeQUlBBFVxKHTI= +github.com/moricho/tparallel v0.3.2/go.mod h1:OQ+K3b4Ln3l2TZveGCywybl68glfLEwFGqvnjok8b+U= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= @@ -448,6 +469,8 @@ github.com/polyfloyd/go-errorlint v1.5.1 h1:5gHxDjLyyWij7fhfrjYNNlHsUNQeyx0LFQKU github.com/polyfloyd/go-errorlint v1.5.1/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= github.com/polyfloyd/go-errorlint v1.5.2 h1:SJhVik3Umsjh7mte1vE0fVZ5T1gznasQG3PV7U5xFdA= github.com/polyfloyd/go-errorlint v1.5.2/go.mod h1:sH1QC1pxxi0fFecsVIzBmxtrgd9IF/SkJpA6wqyKAJs= +github.com/polyfloyd/go-errorlint v1.6.0 h1:tftWV9DE7txiFzPpztTAwyoRLKNj9gpVm2cg8/OwcYY= +github.com/polyfloyd/go-errorlint v1.6.0/go.mod h1:HR7u8wuP1kb1NeN1zqTd1ZMlqUKPPHF+Id4vIPvDqVw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= @@ -486,6 +509,8 @@ github.com/ryancurrah/gomodguard v1.3.1 h1:fH+fUg+ngsQO0ruZXXHnA/2aNllWA1whly4a6 github.com/ryancurrah/gomodguard v1.3.1/go.mod h1:DGFHzEhi6iJ0oIDfMuo3TgrS+L9gZvrEfmjjuelnRU0= github.com/ryancurrah/gomodguard v1.3.2 h1:CuG27ulzEB1Gu5Dk5gP8PFxSOZ3ptSdP5iI/3IXxM18= github.com/ryancurrah/gomodguard v1.3.2/go.mod h1:LqdemiFomEjcxOqirbQCb3JFvSxH2JUYMerTFd3sF2o= +github.com/ryancurrah/gomodguard v1.3.3 h1:eiSQdJVNr9KTNxY2Niij8UReSwR8Xrte3exBrAZfqpg= +github.com/ryancurrah/gomodguard v1.3.3/go.mod h1:rsKQjj4l3LXe8N344Ow7agAy5p9yjsWOtRzUMYmA0QY= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= github.com/sanposhiho/wastedassign/v2 v2.0.7 h1:J+6nrY4VW+gC9xFzUc+XjPD3g3wF3je/NsJFwFK7Uxc= @@ -498,6 +523,8 @@ github.com/sashamelentyev/usestdlibvars v1.25.0 h1:IK8SI2QyFzy/2OD2PYnhy84dpfNo9 github.com/sashamelentyev/usestdlibvars v1.25.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= github.com/sashamelentyev/usestdlibvars v1.26.0 h1:LONR2hNVKxRmzIrZR0PhSF3mhCAzvnr+DcUiHgREfXE= github.com/sashamelentyev/usestdlibvars v1.26.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= +github.com/sashamelentyev/usestdlibvars v1.27.0 h1:t/3jZpSXtRPRf2xr0m63i32ZrusyurIGT9E5wAvXQnI= +github.com/sashamelentyev/usestdlibvars v1.27.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= github.com/securego/gosec/v2 v2.19.0 h1:gl5xMkOI0/E6Hxx0XCY2XujA3V7SNSefA8sC+3f1gnk= github.com/securego/gosec/v2 v2.19.0/go.mod h1:hOkDcHz9J/XIgIlPDXalxjeVYsHxoWUc5zJSHxcB8YM= github.com/securego/gosec/v2 v2.20.1-0.20240525090044-5f0084eb01a9 h1:rnO6Zp1YMQwv8AyxzuwsVohljJgp4L0ZqiCgtACsPsc= @@ -515,6 +542,8 @@ github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+W github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sivchari/tenv v1.7.1 h1:PSpuD4bu6fSmtWMxSGWcvqUUgIn7k3yOJhOIzVWn8Ak= github.com/sivchari/tenv v1.7.1/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= +github.com/sivchari/tenv v1.10.0 h1:g/hzMA+dBCKqGXgW8AV/1xIWhAvDrx0zFKNR48NFMg0= +github.com/sivchari/tenv v1.10.0/go.mod h1:tdY24masnVoZFxYrHv/nD6Tc8FbkEtAQEEziXpyMgqY= github.com/sonatard/noctx v0.0.2 h1:L7Dz4De2zDQhW8S0t+KUjY0MAQJd6SgVwhzNIc4ok00= github.com/sonatard/noctx v0.0.2/go.mod h1:kzFz+CzWSjQ2OzIm46uJZoXuBpa2+0y3T36U18dWqIo= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= @@ -525,6 +554,8 @@ github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -577,6 +608,8 @@ github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/ github.com/ultraware/whitespace v0.1.1/go.mod h1:XcP1RLD81eV4BW8UhQlpaR+SDc2givTvyI8a586WjW8= github.com/uudashr/gocognit v1.1.2 h1:l6BAEKJqQH2UpKAPKdMfZf5kE4W/2xk8pfU1OVLvniI= github.com/uudashr/gocognit v1.1.2/go.mod h1:aAVdLURqcanke8h3vg35BC++eseDm66Z7KmchI5et4k= +github.com/uudashr/gocognit v1.1.3 h1:l+a111VcDbKfynh+airAy/DJQKaXh2m9vkoysMPSZyM= +github.com/uudashr/gocognit v1.1.3/go.mod h1:aKH8/e8xbTRBwjbCkwZ8qt4l2EpKXl31KMHgSS+lZ2U= github.com/xen0n/gosmopolitan v1.2.2 h1:/p2KTnMzwRexIW8GlKawsTWOxn7UHA+jCMF/V8HHtvU= github.com/xen0n/gosmopolitan v1.2.2/go.mod h1:7XX7Mj61uLYrj0qmeN0zi7XDon9JRAEhYQqAPLVNTeg= github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= @@ -608,6 +641,8 @@ go-simpler.org/sloglint v0.7.0 h1:rMZRxD9MbaGoRFobIOicMxZzum7AXNFDlez6xxJs5V4= go-simpler.org/sloglint v0.7.0/go.mod h1:g9SXiSWY0JJh4LS39/Q0GxzP/QX2cVcbTOYhDpXrJEs= go-simpler.org/sloglint v0.7.1 h1:qlGLiqHbN5islOxjeLXoPtUdZXb669RW+BDQ+xOSNoU= go-simpler.org/sloglint v0.7.1/go.mod h1:OlaVDRh/FKKd4X4sIMbsz8st97vomydceL146Fthh/c= +go-simpler.org/sloglint v0.7.2 h1:Wc9Em/Zeuu7JYpl+oKoYOsQSy2X560aVueCW/m6IijY= +go-simpler.org/sloglint v0.7.2/go.mod h1:US+9C80ppl7VsThQclkM7BkCHQAzuz8kHLsW3ppuluo= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -679,6 +714,8 @@ golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= +golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -740,6 +777,8 @@ golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -800,6 +839,8 @@ golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= +golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -890,6 +931,8 @@ golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= +golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -997,6 +1040,8 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= +honnef.co/go/tools v0.5.0 h1:29uoiIormS3Z6R+t56STz/oI4v+mB51TSmEOdJPgRnE= +honnef.co/go/tools v0.5.0/go.mod h1:e9irvo83WDG9/irijV44wr3tbhcFeRnfpVlRqVwpzMs= mvdan.cc/gofumpt v0.6.0 h1:G3QvahNDmpD+Aek/bNOLrFR2XC6ZAdo62dZu65gmwGo= mvdan.cc/gofumpt v0.6.0/go.mod h1:4L0wf+kgIPZtcCWXynNS2e6bhmj73umwnuXSZarixzA= mvdan.cc/unparam v0.0.0-20240104100049-c549a3470d14 h1:zCr3iRRgdk5eIikZNDphGcM6KGVTx3Yu+/Uu9Es254w= diff --git a/.bingo/variables.env b/.bingo/variables.env index 2df4e49c0ba..2f31fa8a2e7 100644 --- a/.bingo/variables.env +++ b/.bingo/variables.env @@ -14,7 +14,7 @@ CUE="${GOBIN}/cue-v0.5.0" DRONE="${GOBIN}/drone-v1.5.0" -GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.59.1" +GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.60.1" JB="${GOBIN}/jb-v0.5.1" diff --git a/.drone.yml b/.drone.yml index 861c6d08fc0..10cd4270992 100644 --- a/.drone.yml +++ b/.drone.yml @@ -25,7 +25,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - ./bin/build verify-drone @@ -76,14 +76,14 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - go install github.com/bazelbuild/buildtools/buildifier@latest - buildifier --lint=warn -mode=check -r . depends_on: - compile-build-cmd - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: lint-starlark trigger: event: @@ -377,7 +377,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -386,21 +386,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: test-backend - commands: - apk add --update build-base @@ -409,7 +409,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: test-backend-integration trigger: event: @@ -461,7 +461,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - apk add --update curl jq bash @@ -488,16 +488,16 @@ steps: - apk add --update make - make gen-go depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: validate-openapi-spec trigger: event: @@ -556,7 +556,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -566,7 +566,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -575,14 +575,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - yarn install --immutable || yarn install --immutable @@ -615,7 +615,7 @@ steps: from_secret: drone_token - commands: - /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64 - -a targz:grafana:linux/arm/v7 --go-version=1.22.4 --yarn-cache=$$YARN_CACHE_FOLDER + -a targz:grafana:linux/arm/v7 --go-version=1.23.0 --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER --grafana-dir=$$PWD > packages.txt depends_on: - yarn-install @@ -851,7 +851,7 @@ steps: - /src/grafana-build artifacts -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu -a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7 -a docker:grafana:linux/arm/v7:ubuntu --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER - --go-version=1.22.4 --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.19.1 --tag-format='{{ + --go-version=1.23.0 --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.19.1 --tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --grafana-dir=$$PWD --ubuntu-tag-format='{{ .version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' > docker.txt - find ./dist -name '*docker*.tar.gz' -type f | xargs -n1 docker load -i @@ -995,7 +995,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -1009,7 +1009,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1018,14 +1018,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -1046,7 +1046,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql57:3306 -timeout 120s @@ -1067,7 +1067,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql57 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: mysql-5.7-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -1088,7 +1088,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -1104,7 +1104,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -1120,7 +1120,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -1137,7 +1137,7 @@ steps: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 failure: ignore - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: remote-alertmanager-integration-tests trigger: event: @@ -1225,7 +1225,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue trigger: event: @@ -1266,7 +1266,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - apt-get update -yq && apt-get install shellcheck @@ -1338,7 +1338,7 @@ steps: environment: GITHUB_TOKEN: from_secret: github_token - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: swagger-gen trigger: event: @@ -1434,7 +1434,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -1445,7 +1445,7 @@ steps: - CODEGEN_VERIFY=1 make gen-cue depends_on: - clone-enterprise - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1455,14 +1455,14 @@ steps: - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: - clone-enterprise - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - apk add --update build-base @@ -1470,7 +1470,7 @@ steps: - go test -v -run=^$ -benchmem -timeout=1h -count=8 -bench=. ${GO_PACKAGES} depends_on: - wire-install - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: sqlite-benchmark-integration-tests - commands: - apk add --update build-base @@ -1482,7 +1482,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: postgres-benchmark-integration-tests - commands: - apk add --update build-base @@ -1493,7 +1493,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql57 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: mysql-5.7-benchmark-integration-tests - commands: - apk add --update build-base @@ -1504,7 +1504,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: mysql-8.0-benchmark-integration-tests trigger: event: @@ -1582,7 +1582,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue trigger: branch: main @@ -1755,7 +1755,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1764,21 +1764,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: test-backend - commands: - apk add --update build-base @@ -1787,7 +1787,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: test-backend-integration trigger: branch: main @@ -1832,22 +1832,22 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - apk add --update make - make gen-go depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: validate-openapi-spec - commands: - ./bin/build verify-drone @@ -1964,7 +1964,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -1974,7 +1974,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1983,14 +1983,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - yarn install --immutable || yarn install --immutable @@ -2022,7 +2022,7 @@ steps: name: build-frontend-packages - commands: - /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64 - -a targz:grafana:linux/arm/v7 --go-version=1.22.4 --yarn-cache=$$YARN_CACHE_FOLDER + -a targz:grafana:linux/arm/v7 --go-version=1.23.0 --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER --grafana-dir=$$PWD > packages.txt depends_on: - update-package-json-version @@ -2294,7 +2294,7 @@ steps: - /src/grafana-build artifacts -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu -a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7 -a docker:grafana:linux/arm/v7:ubuntu --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER - --go-version=1.22.4 --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.19.1 --tag-format='{{ + --go-version=1.23.0 --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.19.1 --tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --grafana-dir=$$PWD --ubuntu-tag-format='{{ .version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' > docker.txt - find ./dist -name '*docker*.tar.gz' -type f | xargs -n1 docker load -i @@ -2500,7 +2500,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -2514,7 +2514,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -2523,14 +2523,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -2551,7 +2551,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql57:3306 -timeout 120s @@ -2572,7 +2572,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql57 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: mysql-5.7-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -2593,7 +2593,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -2609,7 +2609,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -2625,7 +2625,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -2642,7 +2642,7 @@ steps: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 failure: ignore - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: remote-alertmanager-integration-tests trigger: branch: main @@ -2952,7 +2952,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -2961,21 +2961,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: test-backend - commands: - apk add --update build-base @@ -2984,7 +2984,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: test-backend-integration trigger: branch: @@ -3027,22 +3027,22 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - apk add --update make - make gen-go depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: validate-openapi-spec trigger: branch: @@ -3132,7 +3132,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -3146,7 +3146,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -3155,14 +3155,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -3183,7 +3183,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql57:3306 -timeout 120s @@ -3204,7 +3204,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql57 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: mysql-5.7-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -3225,7 +3225,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -3241,7 +3241,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -3257,7 +3257,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -3274,7 +3274,7 @@ steps: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 failure: ignore - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: remote-alertmanager-integration-tests trigger: branch: @@ -3377,7 +3377,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - ./bin/build artifacts docker fetch --edition oss @@ -3508,7 +3508,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - ./bin/build artifacts docker fetch --edition oss @@ -3644,7 +3644,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - ./bin/build artifacts packages --tag $${DRONE_TAG} --src-bucket $${PRERELEASE_BUCKET} @@ -3729,7 +3729,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - yarn install --immutable || yarn install --immutable @@ -3953,7 +3953,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - depends_on: - compile-build-cmd @@ -4171,7 +4171,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.22.4 + GO_VERSION: 1.23.0 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4229,13 +4229,13 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: compile-build-cmd - commands: - ./bin/build whatsnew-checker depends_on: - compile-build-cmd - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: whats-new-checker trigger: event: @@ -4337,7 +4337,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -4346,21 +4346,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: test-backend - commands: - apk add --update build-base @@ -4369,7 +4369,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: test-backend-integration trigger: event: @@ -4426,7 +4426,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.22.4 + GO_VERSION: 1.23.0 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4609,7 +4609,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.22.4 + GO_VERSION: 1.23.0 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4758,7 +4758,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -4767,21 +4767,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: test-backend - commands: - apk add --update build-base @@ -4790,7 +4790,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: test-backend-integration trigger: cron: @@ -4845,7 +4845,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.22.4 + GO_VERSION: 1.23.0 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4992,7 +4992,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.22.4 + GO_VERSION: 1.23.0 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -5081,7 +5081,7 @@ steps: - commands: - 'dagger run --silent /src/grafana-build artifacts -a $${ARTIFACTS} --grafana-ref=$${GRAFANA_REF} --enterprise-ref=$${ENTERPRISE_REF} --grafana-repo=$${GRAFANA_REPO} --version=$${VERSION} ' - - --go-version=1.22.4 + - --go-version=1.23.0 environment: _EXPERIMENTAL_DAGGER_CLOUD_TOKEN: from_secret: dagger_token @@ -5102,7 +5102,7 @@ steps: from_secret: gcp_key_base64 GITHUB_TOKEN: from_secret: github_token - GO_VERSION: 1.22.4 + GO_VERSION: 1.23.0 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -5192,20 +5192,20 @@ steps: - commands: [] depends_on: - clone - image: golang:1.22.4-windowsservercore-1809 + image: golang:1.23.0-windowsservercore-1809 name: windows-init - commands: - go install github.com/google/wire/cmd/wire@v0.5.0 - wire gen -tags oss ./pkg/server depends_on: - windows-init - image: golang:1.22.4-windowsservercore-1809 + image: golang:1.23.0-windowsservercore-1809 name: wire-install - commands: - go test -short -covermode=atomic -timeout=5m ./pkg/... depends_on: - wire-install - image: golang:1.22.4-windowsservercore-1809 + image: golang:1.23.0-windowsservercore-1809 name: test-backend trigger: event: @@ -5298,7 +5298,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -5307,14 +5307,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -5335,7 +5335,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql57:3306 -timeout 120s @@ -5356,7 +5356,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql57 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: mysql-5.7-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -5377,7 +5377,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -5393,7 +5393,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -5409,7 +5409,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -5426,7 +5426,7 @@ steps: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 failure: ignore - image: golang:1.22.4-alpine + image: golang:1.23.0-alpine name: remote-alertmanager-integration-tests trigger: event: @@ -5781,7 +5781,7 @@ steps: - commands: - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM docker:27-cli - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine/git:2.40.1 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.22.4-alpine + - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.23.0-alpine - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:20.9.0-alpine - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:20-bookworm - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM google/cloud-sdk:431.0.0 @@ -5818,7 +5818,7 @@ steps: - commands: - trivy --exit-code 1 --severity HIGH,CRITICAL docker:27-cli - trivy --exit-code 1 --severity HIGH,CRITICAL alpine/git:2.40.1 - - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.22.4-alpine + - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.23.0-alpine - trivy --exit-code 1 --severity HIGH,CRITICAL node:20.9.0-alpine - trivy --exit-code 1 --severity HIGH,CRITICAL node:20-bookworm - trivy --exit-code 1 --severity HIGH,CRITICAL google/cloud-sdk:431.0.0 @@ -6074,6 +6074,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: 39565be86e2be3f42728062ce9c4e4b28d5f386e5f48cf3c765fd443c44d9e0e +hmac: cd0bc27b34a09de191974f360d43b55324bd88d20c4fe92f7c41df56394fc25a ... diff --git a/.github/workflows/go_lint.yml b/.github/workflows/go_lint.yml index 4cdbabf22b3..d4ac5e49346 100644 --- a/.github/workflows/go_lint.yml +++ b/.github/workflows/go_lint.yml @@ -25,8 +25,8 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.59.1 + version: v1.60.1 args: | - --config .golangci.toml --max-same-issues=0 --max-issues-per-linter=0 --verbose $(./scripts/go-workspace/golangci-lint-includes.sh) + --config .golangci.toml --max-same-issues=0 --max-issues-per-linter=0 --verbose $(./scripts/go-workspace/golangci-lint-includes.sh) skip-cache: true install-mode: binary diff --git a/Dockerfile b/Dockerfile index 5fe3fc478aa..1240f4c53eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ ARG BASE_IMAGE=alpine:3.19.1 ARG JS_IMAGE=node:20-alpine ARG JS_PLATFORM=linux/amd64 -ARG GO_IMAGE=golang:1.22.4-alpine +ARG GO_IMAGE=golang:1.23.0-alpine ARG GO_SRC=go-builder ARG JS_SRC=js-builder diff --git a/Makefile b/Makefile index 1424ea96ce0..27834bf0db8 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ WIRE_TAGS = "oss" include .bingo/Variables.mk GO = go -GO_VERSION = 1.22.4 +GO_VERSION = 1.23.0 GO_LINT_FILES ?= $(shell ./scripts/go-workspace/golangci-lint-includes.sh) GO_TEST_FILES ?= $(shell ./scripts/go-workspace/test-includes.sh) SH_FILES ?= $(shell find ./scripts -name *.sh) diff --git a/go.mod b/go.mod index 1b42a5dc945..20e3ff95352 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana -go 1.22.4 +go 1.23.0 // contains openapi encoder fixes. remove ASAP replace cuelang.org/go => github.com/grafana/cue v0.0.0-20230926092038-971951014e3f // @grafana/grafana-as-code diff --git a/go.work b/go.work index 5e9b02fc37a..6d2d055536e 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.22.4 +go 1.23.0 // The `skip:golangci-lint` comment tag is used to exclude the package from the `golangci-lint` GitHub Action. // The module at the root of the repo (`.`) is excluded because ./pkg/... is included manually in the `golangci-lint` configuration. diff --git a/go.work.sum b/go.work.sum index 15a623ca31d..e9b2399c1f8 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,7 +1,5 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1 h1:tdpHgTbmbvEIARu+bixzmleMi14+3imnpoFXz+Qzjp4= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= -buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1 h1:vp9EaPFSb75qe/793x58yE5fY1IJ/gdxb/kcDUzavtI= -buf.build/gen/go/grpc-ecosystem/grpc-gateway/bufbuild/connect-go v1.4.1-20221127060915-a1ecdc58eccd.1/go.mod h1:YDq2B5X5BChU0lxAG5MxHpDb8mx1fv9OGtF2mwOe7hY= cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= cloud.google.com/go/accessapproval v1.7.5 h1:uzmAMSgYcnlHa9X9YSQZ4Q1wlfl4NNkZyQgho1Z6p04= @@ -253,7 +251,6 @@ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA= github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo= github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog= -github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2/go.mod h1:At5TxYYdxkbQL0TSefRjhLE3Q0lgvqKKMSFUglJ7i1U= github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw= github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= @@ -269,8 +266,6 @@ github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe h1:QQ3GSy+MqSHxm/d8nC github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= -github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= -github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= @@ -328,14 +323,11 @@ github.com/elastic/go-sysinfo v1.11.2 h1:mcm4OSYVMyws6+n2HIVMGkln5HOpo5Ie1ZmbbNn github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ= 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 v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= github.com/expr-lang/expr v1.16.2 h1:JvMnzUs3LeVHBvGFcXYmXo+Q6DPDmzrlcSBO6Wy3w4s= github.com/expr-lang/expr v1.16.2/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= -github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= -github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= @@ -347,7 +339,6 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS github.com/fsouza/fake-gcs-server v1.7.0 h1:Un0BXUXrRWYSmYyC1Rqm2e2WJfTPyDy/HGMz31emTi8= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= -github.com/getkin/kin-openapi v0.124.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= @@ -367,8 +358,6 @@ github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM= -github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -441,7 +430,6 @@ github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSAS github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 h1:KwWnWVWCNtNq/ewIX7HIKnELmEx2nDP42yskD/pi7QE= github.com/influxdata/influxdb v1.7.6 h1:8mQ7A/V+3noMGCt/P9pD09ISaiz9XvgCk303UYA3gcs= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig= -github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= @@ -626,8 +614,6 @@ github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwy github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE= -github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= -github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= @@ -666,7 +652,6 @@ github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0b github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc= -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/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= @@ -739,8 +724,6 @@ github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= -go.etcd.io/gofail v0.1.0 h1:XItAMIhOojXFQMgrxjnd2EIIHun/d5qL0Pf7FzVTkFg= -go.etcd.io/gofail v0.1.0/go.mod h1:VZBCXYGZhHAinaBiiqYvuDynvahNsAyLFwB3kEHKz1M= go.opentelemetry.io/collector v0.97.0 h1:qyOju13byHIKEK/JehmTiGMj4pFLa4kDyrOCtTmjHU0= go.opentelemetry.io/collector v0.97.0/go.mod h1:V6xquYAaO2VHVu4DBK28JYuikRdZajh7DH5Vl/Y8NiA= go.opentelemetry.io/collector/component v0.97.0 h1:vanKhXl5nptN8igRH4PqVYHOILif653vaPIKv6LCZCI= @@ -806,7 +789,6 @@ go.opentelemetry.io/collector/service v0.95.0/go.mod h1:4yappQmDE5UZmLE9wwtj6IPM go.opentelemetry.io/contrib/config v0.4.0 h1:Xb+ncYOqseLroMuBesGNRgVQolXcXOhMj7EhGwJCdHs= go.opentelemetry.io/contrib/config v0.4.0/go.mod h1:drNk2xRqLWW4/amk6Uh1S+sDAJTc7bcEEN1GfJzj418= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.51.0/go.mod h1:27iA5uvhuRNmalO+iEUdVn5ZMj2qy10Mm+XRIpRmyuU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= go.opentelemetry.io/contrib/propagators/b3 v1.23.0 h1:aaIGWc5JdfRGpCafLRxMJbD65MfTa206AwSKkvGS0Hg= go.opentelemetry.io/contrib/propagators/b3 v1.23.0/go.mod h1:Gyz7V7XghvwTq+mIhLFlTgcc03UDroOg8vezs4NLhwU= @@ -818,7 +800,6 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1 h1:ZqR go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1/go.mod h1:D7ynngPWlGJrqyGSDOdscuv7uqttfCE3jcBvffDv9y4= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.23.1 h1:q/Nj5/2TZRIt6PderQ9oU0M00fzoe8UZuINGw6ETGTw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.23.1/go.mod h1:DTE9yAu6r08jU3xa68GiSeI7oRcSEQ2RpKbbQGO+dWM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ= go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ= go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.23.1 h1:C8r95vDR125t815KD+b1tI0Fbc1pFnwHTBxkbIZ6Szc= @@ -833,19 +814,15 @@ go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= -golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -856,7 +833,6 @@ golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= @@ -886,7 +862,6 @@ gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM= howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= @@ -895,7 +870,6 @@ k8s.io/code-generator v0.31.0/go.mod h1:84y4w3es8rOJOUUP1rLsIiGlO1JuEaPFXQPA9e/K k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/shsbSYiWXmNGlks= k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= -k8s.io/kube-openapi v0.0.0-20240220201932-37d671a357a5/go.mod h1:Pa1PvrP7ACSkuX6I7KYomY6cmMA0Tx86waBhDUgoKPw= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= diff --git a/hack/go.mod b/hack/go.mod index e5507f966e0..32b2a9e2a43 100644 --- a/hack/go.mod +++ b/hack/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/hack -go 1.22.4 +go 1.23.0 require k8s.io/code-generator v0.31.0 diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index dfafb0b8a29..ca4483da864 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/aggregator -go 1.22.4 +go 1.23.0 require ( github.com/emicklei/go-restful/v3 v3.11.0 diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index a4d1e3cc2d6..77559cf6a89 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -1,523 +1,182 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= -github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE= -github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= -github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= -github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071 h1:RdCf9hH3xq5vJifrjGB7zQlFkdRB3pAppcX2helDq2U= -github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= -github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/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 v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M= -github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= -github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac h1:9yrT5tmn9Zc0ytWPASlaPwQfQMQYnRf0RSDe1XvHw0Q= -github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 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= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/getkin/kin-openapi v0.125.0 h1:jyQCyf2qXS1qvs2U00xQzkGCqYPhEhZDmSmVt65fXno= -github.com/getkin/kin-openapi v0.125.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= -github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= -github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= -github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= -github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= -github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= -github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 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.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/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= github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= -github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= -github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 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.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= -github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/grafana-plugin-sdk-go v0.244.0 h1:ZZxHbiiF6QcsnlbPFyZGmzNDoTC1pLeHXUQYoskWt5c= 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= -github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435/go.mod h1:8cz+z0i57IjN6MYmu/zZQdCg9CQcsnEHbaJBBEf3KQo= 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= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= -github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= -github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= -github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= -github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= -github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= -github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= -github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= -github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= -github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= -github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= -github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= -github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= -github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= -github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= -github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= -github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= -github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= -github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= -github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= -github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= -github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a h1:vcrhXnj9g9PIE+cmZgaPSwOyJ8MAQTRmsgGrB0x5rF4= -github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM= -github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= -github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= -github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= -go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= -go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= -go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= go.etcd.io/etcd/client/v2 v2.305.13 h1:RWfV1SX5jTU0lbCvpVQe3iPQeAHETWdOTb6pxhd77C8= -go.etcd.io/etcd/client/v2 v2.305.13/go.mod h1:iQnL7fepbiomdXMb3om1rHq96htNNGv2sJkEcZGDRRg= go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= -go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= go.etcd.io/etcd/pkg/v3 v3.5.13 h1:st9bDWNsKkBNpP4PR1MvM/9NqUPfvYZx/YXegsYEH8M= -go.etcd.io/etcd/pkg/v3 v3.5.13/go.mod h1:N+4PLrp7agI/Viy+dUYpX7iRtSPvKq+w8Y14d1vX+m0= go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= -go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= -go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.53.0 h1:IVtyPth4Rs5P8wIf0mP2KVKFNTJ4paX9qQ4Hkh5gFdc= -go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.53.0/go.mod h1:ImRBLMJv177/pwiLZ7tU7HDGNdBv7rS0HQ99eN/zBl8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/contrib/propagators/jaeger v1.28.0 h1:xQ3ktSVS128JWIaN1DiPGIjcH+GsvkibIAVRWFjS9eM= -go.opentelemetry.io/contrib/propagators/jaeger v1.28.0/go.mod h1:O9HIyI2kVBrFoEwQZ0IN6PHXykGoit4mZV2aEjkTRH4= go.opentelemetry.io/contrib/samplers/jaegerremote v0.22.0 h1:OYxqumWcd1yaV/qvCt1B7Sru9OeUNGjeXq/oldx3AGk= -go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= -go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= -go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= -go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= -go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= -go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= -go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= -go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= -go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= -golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= -golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= -golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= -golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= -golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= -gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= -google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= -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= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= -gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= -k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= -k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/apiserver v0.31.0 h1:p+2dgJjy+bk+B1Csz+mc2wl5gHwvNkC9QJV+w55LVrY= -k8s.io/apiserver v0.31.0/go.mod h1:KI9ox5Yu902iBnnyMmy7ajonhKnkeZYJhTZ/YI+WEMk= k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= -k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs= -k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= -k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/api/frontend_logging.go b/pkg/api/frontend_logging.go index 7ad646fc815..a71c9d3ae0c 100644 --- a/pkg/api/frontend_logging.go +++ b/pkg/api/frontend_logging.go @@ -30,7 +30,7 @@ func GrafanaJavascriptAgentLogMessageHandler(store *frontendlogging.SourceMapSto // Meta object is standard across event types, adding it globally. - if event.Logs != nil && len(event.Logs) > 0 { + if len(event.Logs) > 0 { for _, logEntry := range event.Logs { var ctx = frontendlogging.CtxVector{} ctx = event.AddMetaToContext(ctx) @@ -64,7 +64,7 @@ func GrafanaJavascriptAgentLogMessageHandler(store *frontendlogging.SourceMapSto } } - if event.Measurements != nil && len(event.Measurements) > 0 { + if len(event.Measurements) > 0 { for _, measurementEntry := range event.Measurements { for measurementName, measurementValue := range measurementEntry.Values { var ctx = frontendlogging.CtxVector{} @@ -75,7 +75,7 @@ func GrafanaJavascriptAgentLogMessageHandler(store *frontendlogging.SourceMapSto } } } - if event.Exceptions != nil && len(event.Exceptions) > 0 { + if len(event.Exceptions) > 0 { for _, exception := range event.Exceptions { var ctx = frontendlogging.CtxVector{} ctx = event.AddMetaToContext(ctx) diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 24eb29c5890..cab2c972230 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/apimachinery -go 1.22.4 +go 1.23.0 require ( github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db // @grafana/identity-access-team diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 80c55c1b509..668022448f5 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/apiserver -go 1.22.4 +go 1.23.0 require ( github.com/google/go-cmp v0.6.0 diff --git a/pkg/build/go.mod b/pkg/build/go.mod index 1a1d36e653c..0cf69c6189e 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/build -go 1.22.4 +go 1.23.0 // Override docker/docker to avoid: // go: github.com/drone-runners/drone-runner-docker@v1.8.2 requires diff --git a/pkg/build/wire/go.mod b/pkg/build/wire/go.mod index 1aef58c93d1..9d3457f15bd 100644 --- a/pkg/build/wire/go.mod +++ b/pkg/build/wire/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/build/wire -go 1.22.4 +go 1.23.0 require ( github.com/google/go-cmp v0.6.0 diff --git a/pkg/build/wire/internal/wire/testdata/UnexportedStruct/want/wire_errs.txt b/pkg/build/wire/internal/wire/testdata/UnexportedStruct/want/wire_errs.txt index ae23f88c391..41c0cfaf75a 100644 --- a/pkg/build/wire/internal/wire/testdata/UnexportedStruct/want/wire_errs.txt +++ b/pkg/build/wire/internal/wire/testdata/UnexportedStruct/want/wire_errs.txt @@ -1 +1 @@ -example.com/foo/wire.go:x:y: foo not exported by package bar \ No newline at end of file +example.com/foo/wire.go:x:y: name foo not exported by package bar \ No newline at end of file diff --git a/pkg/cmd/grafana-cli/commands/conflict_user_command.go b/pkg/cmd/grafana-cli/commands/conflict_user_command.go index 1839dc7013f..01eb1336ec4 100644 --- a/pkg/cmd/grafana-cli/commands/conflict_user_command.go +++ b/pkg/cmd/grafana-cli/commands/conflict_user_command.go @@ -85,12 +85,7 @@ func initializeConflictResolver(cmd *utils.ContextCommandLine, f Formatter, ctx return nil, fmt.Errorf("%v: %w", "failed to get user service", err) } routing := routing.ProvideRegister() - if err != nil { - return nil, fmt.Errorf("%v: %w", "failed to initialize tracer config", err) - } - if err != nil { - return nil, fmt.Errorf("%v: %w", "failed to initialize tracer service", err) - } + acService, err := acimpl.ProvideService(cfg, replstore, routing, nil, nil, nil, features, tracer, zanzana.NewNoopClient(), permreg.ProvidePermissionRegistry()) if err != nil { return nil, fmt.Errorf("%v: %w", "failed to get access control", err) @@ -123,9 +118,9 @@ func runListConflictUsers() func(context *cli.Context) error { logger.Info(color.GreenString("No Conflicting users found.\n\n")) return nil } - logger.Infof("\n\nShowing conflicts\n\n") - logger.Infof(r.ToStringPresentation()) - logger.Infof("\n") + logger.Info("\n\nShowing conflicts\n\n") + logger.Info(r.ToStringPresentation()) + logger.Info("\n") if len(r.DiscardedBlocks) != 0 { r.logDiscardedUsers() } @@ -461,7 +456,8 @@ func (r *ConflictResolver) showChanges() { } } b.WriteString("Keep the following user.\n") - b.WriteString(fmt.Sprintf("%s\n", block)) + b.WriteString(block) + b.WriteByte('\n') b.WriteString(color.GreenString(fmt.Sprintf("id: %s, email: %s, login: %s\n", mainUser.ID, mainUser.Email, mainUser.Login))) for _, r := range fmt.Sprintf("%s%s", mainUser.Email, mainUser.Login) { if unicode.IsUpper(r) { @@ -482,7 +478,7 @@ func (r *ConflictResolver) showChanges() { b.WriteString("\n\n") } logger.Info("\n\nChanges that will take place\n\n") - logger.Infof(b.String()) + logger.Info(b.String()) } // Formatter make it possible for us to write to terminal and to a file diff --git a/pkg/expr/hysteresis.go b/pkg/expr/hysteresis.go index 685af6ebaf1..d55c78a801d 100644 --- a/pkg/expr/hysteresis.go +++ b/pkg/expr/hysteresis.go @@ -45,7 +45,7 @@ func (h *HysteresisCommand) Execute(ctx context.Context, now time.Time, vars mat if results.IsNoData() { return mathexp.Results{Values: mathexp.Values{mathexp.NewNoData()}}, nil } - if h.LoadedDimensions == nil || len(h.LoadedDimensions) == 0 { + if len(h.LoadedDimensions) == 0 { return h.LoadingThresholdFunc.Execute(traceCtx, now, vars, tracer) } var loadedVals, unloadedVals mathexp.Values diff --git a/pkg/infra/filestorage/test_utils.go b/pkg/infra/filestorage/test_utils.go index 0445807a094..806741ac6e4 100644 --- a/pkg/infra/filestorage/test_utils.go +++ b/pkg/infra/filestorage/test_utils.go @@ -275,7 +275,7 @@ func handleQuery(t *testing.T, ctx context.Context, query interface{}, queryName file, fileFound, err := fs.Get(ctx, inputPath, options) require.NoError(t, err, "%s: should be able to get file %s", queryName, inputPath) - if q.checks != nil && len(q.checks) > 0 { + if len(q.checks) > 0 { require.NotNil(t, file, "%s %s", queryName, inputPath) require.True(t, fileFound, "%s %s", queryName, inputPath) require.Equal(t, strings.ToLower(inputPath), strings.ToLower(file.FullPath), "%s %s", queryName, inputPath) @@ -289,7 +289,7 @@ func handleQuery(t *testing.T, ctx context.Context, query interface{}, queryName resp, err := fs.List(ctx, inputPath, q.input.paging, q.input.options) require.NoError(t, err, "%s: should be able to list files in %s", queryName, inputPath) require.NotNil(t, resp) - if q.list != nil && len(q.list) > 0 { + if len(q.list) > 0 { runChecks(t, queryName, inputPath, resp, q.list) } else { require.NotNil(t, resp, "%s %s", queryName, inputPath) diff --git a/pkg/infra/filestorage/wrapper_test.go b/pkg/infra/filestorage/wrapper_test.go index 0fd9d55f085..6e013f32c46 100644 --- a/pkg/infra/filestorage/wrapper_test.go +++ b/pkg/infra/filestorage/wrapper_test.go @@ -1,7 +1,6 @@ package filestorage import ( - "fmt" "testing" "github.com/stretchr/testify/require" @@ -35,7 +34,7 @@ func TestFilestorage_getParentFolderPath(t *testing.T) { }, } for _, tt := range tests { - t.Run(fmt.Sprintf(tt.name), func(t *testing.T) { + t.Run(tt.name, func(t *testing.T) { require.Equal(t, tt.expected, getParentFolderPath(tt.path)) }) } diff --git a/pkg/login/social/connectors/generic_oauth.go b/pkg/login/social/connectors/generic_oauth.go index 5d85302dd40..1e386d1d2e2 100644 --- a/pkg/login/social/connectors/generic_oauth.go +++ b/pkg/login/social/connectors/generic_oauth.go @@ -104,7 +104,7 @@ func (s *SocialGenericOAuth) Validate(ctx context.Context, newSettings ssoModels return ssosettings.ErrInvalidOAuthConfig("If Team Ids are configured then Team Ids attribute path and Teams URL must be configured.") } - if info.AllowedGroups != nil && len(info.AllowedGroups) > 0 && info.GroupsAttributePath == "" { + if len(info.AllowedGroups) > 0 && info.GroupsAttributePath == "" { return ssosettings.ErrInvalidOAuthConfig("If Allowed groups is configured then Groups attribute path must be configured.") } @@ -497,7 +497,7 @@ func (s *SocialGenericOAuth) fetchPrivateEmail(ctx context.Context, client *http IsConfirmed bool `json:"is_confirmed"` } - response, err := s.httpGet(ctx, client, fmt.Sprintf(s.info.ApiUrl+"/emails")) + response, err := s.httpGet(ctx, client, s.info.ApiUrl+"/emails") if err != nil { s.log.Error("Error getting email address", "url", s.info.ApiUrl+"/emails", "error", err) return "", fmt.Errorf("%v: %w", "Error getting email address", err) @@ -559,7 +559,7 @@ func (s *SocialGenericOAuth) fetchTeamMembershipsFromDeprecatedTeamsUrl(ctx cont Id int `json:"id"` } - response, err := s.httpGet(ctx, client, fmt.Sprintf(s.info.ApiUrl+"/teams")) + response, err := s.httpGet(ctx, client, s.info.ApiUrl+"/teams") if err != nil { s.log.Error("Error getting team memberships", "url", s.info.ApiUrl+"/teams", "error", err) return []string{}, err @@ -586,7 +586,7 @@ func (s *SocialGenericOAuth) fetchTeamMembershipsFromTeamsUrl(ctx context.Contex return []string{}, nil } - response, err := s.httpGet(ctx, client, fmt.Sprintf(s.teamsUrl)) + response, err := s.httpGet(ctx, client, s.teamsUrl) if err != nil { s.log.Error("Error getting team memberships", "url", s.teamsUrl, "error", err) return nil, err @@ -600,7 +600,7 @@ func (s *SocialGenericOAuth) fetchOrganizations(ctx context.Context, client *htt Login string `json:"login"` } - response, err := s.httpGet(ctx, client, fmt.Sprintf(s.info.ApiUrl+"/orgs")) + response, err := s.httpGet(ctx, client, s.info.ApiUrl+"/orgs") if err != nil { s.log.Error("Error getting organizations", "url", s.info.ApiUrl+"/orgs", "error", err) return nil, false diff --git a/pkg/login/social/connectors/github_oauth.go b/pkg/login/social/connectors/github_oauth.go index 348e59f1ddd..124b642f822 100644 --- a/pkg/login/social/connectors/github_oauth.go +++ b/pkg/login/social/connectors/github_oauth.go @@ -190,7 +190,7 @@ func (s *SocialGithub) fetchPrivateEmail(ctx context.Context, client *http.Clien Verified bool `json:"verified"` } - response, err := s.httpGet(ctx, client, fmt.Sprintf(s.info.ApiUrl+"/emails")) + response, err := s.httpGet(ctx, client, s.info.ApiUrl+"/emails") if err != nil { return "", fmt.Errorf("Error getting email address: %s", err) } @@ -213,7 +213,7 @@ func (s *SocialGithub) fetchPrivateEmail(ctx context.Context, client *http.Clien } func (s *SocialGithub) fetchTeamMemberships(ctx context.Context, client *http.Client) ([]GithubTeam, error) { - url := fmt.Sprintf(s.info.ApiUrl + "/teams?per_page=100") + url := s.info.ApiUrl + "/teams?per_page=100" hasMore := true teams := make([]GithubTeam, 0) @@ -347,7 +347,7 @@ func (s *SocialGithub) UserInfo(ctx context.Context, client *http.Client, token userInfo.Name = data.Name } - organizationsUrl := fmt.Sprintf(s.info.ApiUrl + "/orgs?per_page=100") + organizationsUrl := s.info.ApiUrl + "/orgs?per_page=100" if !s.isTeamMember(ctx, client) { return nil, ErrMissingTeamMembership.Errorf("User is not a member of any of the allowed teams: %v", s.teamIds) diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index d3976e7362d..b5bad3aff38 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/promlib -go 1.22.4 +go 1.23.0 require ( github.com/grafana/dskit v0.0.0-20240805174438-dfa83b4ed2d3 diff --git a/pkg/promlib/querydata/request.go b/pkg/promlib/querydata/request.go index 7865c9afe2c..c7e605315d8 100644 --- a/pkg/promlib/querydata/request.go +++ b/pkg/promlib/querydata/request.go @@ -2,6 +2,7 @@ package querydata import ( "context" + "errors" "fmt" "net/http" "regexp" @@ -14,12 +15,13 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/tracing" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/data/utils/maputil" + "go.opentelemetry.io/otel/trace" + "github.com/grafana/grafana/pkg/promlib/client" "github.com/grafana/grafana/pkg/promlib/intervalv2" "github.com/grafana/grafana/pkg/promlib/models" "github.com/grafana/grafana/pkg/promlib/querydata/exemplar" "github.com/grafana/grafana/pkg/promlib/utils" - "go.opentelemetry.io/otel/trace" ) const legendFormatAuto = "__auto" @@ -255,7 +257,7 @@ func (s *QueryData) instantQuery(ctx context.Context, c *client.Client, q *model // This is only for health check fall back scenario if res.StatusCode != 200 && q.RefId == "__healthcheck__" { return backend.DataResponse{ - Error: fmt.Errorf(res.Status), + Error: errors.New(res.Status), } } diff --git a/pkg/registry/apis/datasource/sub_proxy.go b/pkg/registry/apis/datasource/sub_proxy.go index 5c082fc4a92..7d04fc47285 100644 --- a/pkg/registry/apis/datasource/sub_proxy.go +++ b/pkg/registry/apis/datasource/sub_proxy.go @@ -43,6 +43,6 @@ func (r *subProxyREST) NewConnectOptions() (runtime.Object, bool, string) { func (r *subProxyREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - responder.Error(fmt.Errorf("TODO, proxy: " + r.pluginJSON.ID)) + responder.Error(fmt.Errorf("TODO, proxy: %s", r.pluginJSON.ID)) }), nil } diff --git a/pkg/registry/apis/query/client/plugin.go b/pkg/registry/apis/query/client/plugin.go index dd0e3d2960f..ab1f85dd5c6 100644 --- a/pkg/registry/apis/query/client/plugin.go +++ b/pkg/registry/apis/query/client/plugin.go @@ -102,7 +102,7 @@ func (d *pluginRegistry) GetDatasourceGroupVersion(pluginId string) (schema.Grou var err error gv, ok := d.apis[pluginId] if !ok { - err = fmt.Errorf("no API found for id: " + pluginId) + err = fmt.Errorf("no API found for id: %s", pluginId) } return gv, err } diff --git a/pkg/registry/apis/query/query.go b/pkg/registry/apis/query/query.go index 637de66d152..ec50d046215 100644 --- a/pkg/registry/apis/query/query.go +++ b/pkg/registry/apis/query/query.go @@ -259,7 +259,7 @@ func (b *QueryAPIBuilder) executeConcurrentQueries(ctx context.Context, requests if theErr, ok := r.(error); ok { err = theErr } else if theErrString, ok := r.(string); ok { - err = fmt.Errorf(theErrString) + err = errors.New(theErrString) } else { err = fmt.Errorf("unexpected error - %s", b.userFacingDefaultError) } diff --git a/pkg/semconv/go.mod b/pkg/semconv/go.mod index 52890761c48..1217b154a55 100644 --- a/pkg/semconv/go.mod +++ b/pkg/semconv/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/semconv -go 1.22.4 +go 1.23.0 require go.opentelemetry.io/otel v1.28.0 diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index 87c30e1abbb..e8a53e6bb71 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -293,7 +293,7 @@ func (cmd *SaveExternalServiceRoleCommand) Validate() error { cmd.ExternalServiceID = slugify.Slugify(cmd.ExternalServiceID) // Check and deduplicate permissions - if cmd.Permissions == nil || len(cmd.Permissions) == 0 { + if len(cmd.Permissions) == 0 { return errors.New("no permissions provided") } dedupMap := map[Permission]bool{} diff --git a/pkg/services/annotations/annotationsimpl/composite_store.go b/pkg/services/annotations/annotationsimpl/composite_store.go index 3bcf0724b52..bc59855c61c 100644 --- a/pkg/services/annotations/annotationsimpl/composite_store.go +++ b/pkg/services/annotations/annotationsimpl/composite_store.go @@ -2,12 +2,13 @@ package annotationsimpl import ( "context" + "errors" "fmt" "sort" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/dskit/concurrency" + + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/annotations" "github.com/grafana/grafana/pkg/services/annotations/accesscontrol" ) @@ -88,7 +89,7 @@ func handleJobPanic(logger log.Logger, storeType string, jobErr *error) { errMsg := "concurrent job panic" if jobErr != nil { - err := fmt.Errorf(errMsg) + err := errors.New(errMsg) if panicErr, ok := r.(error); ok { err = fmt.Errorf("%s: %w", errMsg, panicErr) } diff --git a/pkg/services/authz/zanzana/client/client.go b/pkg/services/authz/zanzana/client/client.go index 670ef845da1..6bd77f8afe8 100644 --- a/pkg/services/authz/zanzana/client/client.go +++ b/pkg/services/authz/zanzana/client/client.go @@ -5,11 +5,10 @@ import ( "errors" "fmt" - "google.golang.org/grpc" - "google.golang.org/protobuf/types/known/wrapperspb" - openfgav1 "github.com/openfga/api/proto/openfga/v1" "github.com/openfga/language/pkg/go/transformer" + "google.golang.org/grpc" + "google.golang.org/protobuf/types/known/wrapperspb" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/authz/zanzana/schema" @@ -61,7 +60,7 @@ func New(ctx context.Context, cc grpc.ClientConnInterface, opts ...ClientOption) c.tenantID = "stack-default" } - if c.modules == nil || len(c.modules) == 0 { + if len(c.modules) == 0 { c.modules = schema.SchemaModules } diff --git a/pkg/services/featuremgmt/toggles_gen_test.go b/pkg/services/featuremgmt/toggles_gen_test.go index 32e8357768c..a96b1b5359b 100644 --- a/pkg/services/featuremgmt/toggles_gen_test.go +++ b/pkg/services/featuremgmt/toggles_gen_test.go @@ -248,8 +248,7 @@ func verifyAndGenerateFile(t *testing.T, fpath string, gen string) { body, err := os.ReadFile(fpath) if err == nil { if diff := cmp.Diff(gen, string(body)); diff != "" { - str := fmt.Sprintf("body mismatch (-want +got):\n%s\n", diff) - err = fmt.Errorf(str) + err = fmt.Errorf("body mismatch (-want +got):\n%s\n", diff) } } diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index c6268905661..6bbc3277383 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -164,7 +164,7 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere } // remove user access if empty. Happens if grafana-auth-app is not injected - if sec := treeRoot.FindById(navtree.NavIDCfgAccess); sec != nil && (sec.Children == nil || len(sec.Children) == 0) { + if sec := treeRoot.FindById(navtree.NavIDCfgAccess); sec != nil && len(sec.Children) == 0 { treeRoot.RemoveSectionByID(navtree.NavIDCfgAccess) } diff --git a/pkg/services/ngalert/accesscontrol/models.go b/pkg/services/ngalert/accesscontrol/models.go index 8bad1a0906f..3a5d048bfa6 100644 --- a/pkg/services/ngalert/accesscontrol/models.go +++ b/pkg/services/ngalert/accesscontrol/models.go @@ -14,9 +14,9 @@ var ( ) func NewAuthorizationErrorWithPermissions(action string, eval ac.Evaluator) error { - msg := fmt.Sprintf("user is not authorized to %s", action) - err := ErrAuthorizationBase.Errorf(msg) - err.PublicMessage = msg + msg := "user is not authorized to %s" + err := ErrAuthorizationBase.Errorf(msg, action) + err.PublicMessage = fmt.Sprintf(msg, action) if eval != nil { err.PublicPayload = map[string]any{ "permissions": eval.GoString(), diff --git a/pkg/services/ngalert/api/util.go b/pkg/services/ngalert/api/util.go index 6c320715d19..e62582b4b65 100644 --- a/pkg/services/ngalert/api/util.go +++ b/pkg/services/ngalert/api/util.go @@ -212,8 +212,9 @@ func messageExtractor(resp *response.NormalResponse) (any, error) { // ErrorResp creates a response with a visible error func ErrResp(status int, err error, msg string, args ...any) *response.NormalResponse { if msg != "" { - formattedMsg := fmt.Sprintf(msg, args...) - err = fmt.Errorf("%s: %w", formattedMsg, err) + msg += ": %w" + args = append(args, err) + err = fmt.Errorf(msg, args...) } return response.Error(status, err.Error(), err) } diff --git a/pkg/services/provisioning/plugins/config_reader.go b/pkg/services/provisioning/plugins/config_reader.go index 2b626066b46..52684de6326 100644 --- a/pkg/services/provisioning/plugins/config_reader.go +++ b/pkg/services/provisioning/plugins/config_reader.go @@ -2,6 +2,7 @@ package plugins import ( "context" + "errors" "fmt" "io/fs" "os" @@ -90,18 +91,16 @@ func (cr *configReaderImpl) parsePluginConfig(path string, file fs.DirEntry) (*p func validateRequiredField(apps []*pluginsAsConfig) error { for i := range apps { - var errStrings []string + errs := []error{} for index, app := range apps[i].Apps { if app.PluginID == "" { - errStrings = append( - errStrings, - fmt.Sprintf("app item %d in configuration doesn't contain required field type", index+1), - ) + err := fmt.Errorf("app item %d in configuration doesn't contain required field type", index+1) + errs = append(errs, err) } } - if len(errStrings) != 0 { - return fmt.Errorf(strings.Join(errStrings, "\n")) + if len(errs) != 0 { + return errors.Join(errs...) } } diff --git a/pkg/services/query/query.go b/pkg/services/query/query.go index a911529c9a6..9a54b689054 100644 --- a/pkg/services/query/query.go +++ b/pkg/services/query/query.go @@ -2,6 +2,7 @@ package query import ( "context" + "errors" "fmt" "net/http" "runtime" @@ -125,7 +126,7 @@ func (s *ServiceImpl) executeConcurrentQueries(ctx context.Context, user identit if theErr, ok := r.(error); ok { err = theErr } else if theErrString, ok := r.(string); ok { - err = fmt.Errorf(theErrString) + err = errors.New(theErrString) } else { err = fmt.Errorf("unexpected error - %s", s.cfg.UserFacingDefaultError) } diff --git a/pkg/services/searchusers/searchusers.go b/pkg/services/searchusers/searchusers.go index b912fbecc97..036ac87aee7 100644 --- a/pkg/services/searchusers/searchusers.go +++ b/pkg/services/searchusers/searchusers.go @@ -83,7 +83,7 @@ func (s *OSSService) SearchUser(c *contextmodel.ReqContext) (*user.SearchUserQue } searchQuery := c.Query("query") - filters := make([]user.Filter, 0) + filters := []user.Filter{} for filterName := range s.searchUserFilter.GetFilterList() { filter := s.searchUserFilter.GetFilter(filterName, c.QueryStrings(filterName)) if filter != nil { @@ -112,11 +112,9 @@ func (s *OSSService) SearchUser(c *contextmodel.ReqContext) (*user.SearchUserQue for _, user := range res.Users { user.AvatarURL = dtos.GetGravatarUrl(s.cfg, user.Email) - user.AuthLabels = make([]string, 0) - if user.AuthModule != nil && len(user.AuthModule) > 0 { - for _, authModule := range user.AuthModule { - user.AuthLabels = append(user.AuthLabels, login.GetAuthProviderLabel(authModule)) - } + user.AuthLabels = make([]string, len(user.AuthModule)) + for _, authModule := range user.AuthModule { + user.AuthLabels = append(user.AuthLabels, login.GetAuthProviderLabel(authModule)) } } diff --git a/pkg/services/sqlstore/replstore.go b/pkg/services/sqlstore/replstore.go index 223a5ebc7e3..68854acde0a 100644 --- a/pkg/services/sqlstore/replstore.go +++ b/pkg/services/sqlstore/replstore.go @@ -41,7 +41,7 @@ func (rs *ReplStore) DB() *SQLStore { // ReadReplica returns the read-only SQLStore. If no read replica is configured, // it returns the main SQLStore. func (rs *ReplStore) ReadReplica() *SQLStore { - if rs.repls == nil || len(rs.repls) == 0 { + if len(rs.repls) == 0 { rs.log.Debug("ReadReplica not configured, using main SQLStore") return rs.SQLStore } diff --git a/pkg/services/store/config.go b/pkg/services/store/config.go index d2f86616702..0527aa7fe81 100644 --- a/pkg/services/store/config.go +++ b/pkg/services/store/config.go @@ -126,11 +126,11 @@ type StorageGCSConfig struct { CredentialsFile string `json:"credentialsFile"` } -func newStorage(cfg RootStorageConfig, localWorkCache string) (storageRuntime, error) { +func newStorage(cfg RootStorageConfig, _ string) (storageRuntime, error) { switch cfg.Type { case rootStorageTypeDisk: return newDiskStorage(RootStorageMeta{}, cfg), nil } - return nil, fmt.Errorf("unsupported store: " + cfg.Type) + return nil, fmt.Errorf("unsupported store: %s", cfg.Type) } diff --git a/pkg/services/store/service.go b/pkg/services/store/service.go index fe523f80f52..0e446316fe0 100644 --- a/pkg/services/store/service.go +++ b/pkg/services/store/service.go @@ -153,6 +153,8 @@ func ProvideService( // all externally-defined storages lie under the "content" root root.UnderContentRoot = true + + // TODO: remove unused second argument s, err := newStorage(root, filepath.Join(cfg.DataPath, "storage", "cache", root.Prefix)) if err != nil { grafanaStorageLogger.Warn("Error loading storage config", "error", err) diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index a4a257d34bc..08718f722b9 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -1,6 +1,6 @@ module github.com/grafana/grafana/pkg/storage/unified/resource -go 1.22.4 +go 1.23.0 require ( github.com/fullstorydev/grpchan v1.1.1 diff --git a/pkg/storage/unified/sql/sqltemplate/mocks/test_snapshots.go b/pkg/storage/unified/sql/sqltemplate/mocks/test_snapshots.go index 6fbfe3e9eb3..be97124a5ad 100644 --- a/pkg/storage/unified/sql/sqltemplate/mocks/test_snapshots.go +++ b/pkg/storage/unified/sql/sqltemplate/mocks/test_snapshots.go @@ -10,8 +10,9 @@ import ( "text/template" "github.com/google/go-cmp/cmp" - sqltemplate "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" "github.com/stretchr/testify/require" + + sqltemplate "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) func NewTestingSQLTemplate() sqltemplate.SQLTemplate { @@ -127,7 +128,7 @@ func CheckQuerySnapshots(t *testing.T, setup TemplateTestSetup) { expect, err := os.ReadFile(fpath) if err != nil || len(expect) < 1 { update = true - t.Errorf("missing " + fpath) + t.Error("missing " + fpath) } else { if diff := cmp.Diff(string(expect), clean); diff != "" { t.Errorf("%s: %s", fname, diff) diff --git a/pkg/tsdb/cloudwatch/log_actions.go b/pkg/tsdb/cloudwatch/log_actions.go index 970b55fb491..d4097f4fa85 100644 --- a/pkg/tsdb/cloudwatch/log_actions.go +++ b/pkg/tsdb/cloudwatch/log_actions.go @@ -17,10 +17,10 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana-plugin-sdk-go/experimental/errorsource" + "golang.org/x/sync/errgroup" + "github.com/grafana/grafana/pkg/tsdb/cloudwatch/features" "github.com/grafana/grafana/pkg/tsdb/cloudwatch/models" - - "golang.org/x/sync/errgroup" ) const ( @@ -209,7 +209,7 @@ func (e *cloudWatchExecutor) executeStartQuery(ctx context.Context, logsClient c QueryString: aws.String(modifiedQueryString), } - if logsQuery.LogGroups != nil && len(logsQuery.LogGroups) > 0 && features.IsEnabled(ctx, features.FlagCloudWatchCrossAccountQuerying) { + if len(logsQuery.LogGroups) > 0 && features.IsEnabled(ctx, features.FlagCloudWatchCrossAccountQuerying) { var logGroupIdentifiers []string for _, lg := range logsQuery.LogGroups { arn := lg.Arn diff --git a/pkg/tsdb/cloudwatch/models/settings.go b/pkg/tsdb/cloudwatch/models/settings.go index 4941684917d..58e25d736e4 100644 --- a/pkg/tsdb/cloudwatch/models/settings.go +++ b/pkg/tsdb/cloudwatch/models/settings.go @@ -26,7 +26,7 @@ type CloudWatchSettings struct { func LoadCloudWatchSettings(ctx context.Context, config backend.DataSourceInstanceSettings) (CloudWatchSettings, error) { instance := CloudWatchSettings{} - if config.JSONData != nil && len(config.JSONData) > 1 { + if len(config.JSONData) > 1 { if err := json.Unmarshal(config.JSONData, &instance); err != nil { return CloudWatchSettings{}, fmt.Errorf("could not unmarshal DatasourceSettings json: %w", err) } diff --git a/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine.go b/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine.go index 060e2bcb02e..a05aa3ce5e6 100644 --- a/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine.go +++ b/pkg/tsdb/grafana-postgresql-datasource/sqleng/sql_engine.go @@ -15,11 +15,10 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/data" - "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" - "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" ) // MetaKeyExecutedQueryString is the key where the executed query should get stored @@ -214,7 +213,7 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG if theErr, ok := r.(error); ok { queryResult.dataResponse.Error = theErr } else if theErrString, ok := r.(string); ok { - queryResult.dataResponse.Error = fmt.Errorf(theErrString) + queryResult.dataResponse.Error = errors.New(theErrString) } else { queryResult.dataResponse.Error = fmt.Errorf("unexpected error - %s", e.userError) } diff --git a/pkg/tsdb/grafana-testdata-datasource/sims/engine.go b/pkg/tsdb/grafana-testdata-datasource/sims/engine.go index af3b3c6d479..557484de97f 100644 --- a/pkg/tsdb/grafana-testdata-datasource/sims/engine.go +++ b/pkg/tsdb/grafana-testdata-datasource/sims/engine.go @@ -35,7 +35,7 @@ type SimulationEngine struct { func (s *SimulationEngine) register(info simulationInfo) error { if info.create == nil { - return fmt.Errorf("invalid simulation -- missing create function: " + info.Type) + return fmt.Errorf("invalid simulation -- missing create function: %s", info.Type) } if info.Type == "" { return fmt.Errorf("missing type") diff --git a/pkg/tsdb/influxdb/flux/executor.go b/pkg/tsdb/influxdb/flux/executor.go index 7154dcc5e6b..acad55df636 100644 --- a/pkg/tsdb/influxdb/flux/executor.go +++ b/pkg/tsdb/influxdb/flux/executor.go @@ -41,13 +41,13 @@ func executeQuery(ctx context.Context, logger log.Logger, query queryModel, runn // the error happens, there is not enough info to create a nice error message) var maxPointError maxPointsExceededError if errors.As(dr.Error, &maxPointError) { - text := fmt.Sprintf("A query returned too many datapoints and the results have been truncated at %d points to prevent memory issues. At the current graph size, Grafana can only draw %d.", maxPointError.Count, query.MaxDataPoints) + errMsg := "A query returned too many datapoints and the results have been truncated at %d points to prevent memory issues. At the current graph size, Grafana can only draw %d." // we recommend to the user to use AggregateWindow(), but only if it is not already used if !strings.Contains(query.RawQuery, "aggregateWindow(") { - text += " Try using the aggregateWindow() function in your query to reduce the number of points returned." + errMsg += " Try using the aggregateWindow() function in your query to reduce the number of points returned." } - dr.Error = fmt.Errorf(text) + dr.Error = fmt.Errorf(errMsg, maxPointError.Count, query.MaxDataPoints) } } } diff --git a/pkg/tsdb/influxdb/fsql/arrow_test.go b/pkg/tsdb/influxdb/fsql/arrow_test.go index 3a7a27a0489..32d260a1152 100644 --- a/pkg/tsdb/influxdb/fsql/arrow_test.go +++ b/pkg/tsdb/influxdb/fsql/arrow_test.go @@ -315,7 +315,7 @@ func TestNewFrame(t *testing.T) { }, } if !cmp.Equal(expected, actual, cmp.Comparer(cmpFrame)) { - log.Fatalf(cmp.Diff(expected, actual)) + log.Fatal(cmp.Diff(expected, actual)) } } diff --git a/pkg/tsdb/influxdb/influxql/buffered/response_parser.go b/pkg/tsdb/influxdb/influxql/buffered/response_parser.go index b6b840e6dc5..6d208d324ad 100644 --- a/pkg/tsdb/influxdb/influxql/buffered/response_parser.go +++ b/pkg/tsdb/influxdb/influxql/buffered/response_parser.go @@ -1,6 +1,7 @@ package buffered import ( + "errors" "fmt" "io" "strings" @@ -36,12 +37,12 @@ func parse(buf io.Reader, statusCode int, query *models.Query) *backend.DataResp } if response.Error != "" { - return &backend.DataResponse{Error: fmt.Errorf(response.Error)} + return &backend.DataResponse{Error: errors.New(response.Error)} } result := response.Results[0] if result.Error != "" { - return &backend.DataResponse{Error: fmt.Errorf(result.Error)} + return &backend.DataResponse{Error: errors.New(result.Error)} } if query.ResultFormat == "table" { diff --git a/pkg/tsdb/influxdb/influxql/converter/converter.go b/pkg/tsdb/influxdb/influxql/converter/converter.go index 337b24014b1..fdb49c30902 100644 --- a/pkg/tsdb/influxdb/influxql/converter/converter.go +++ b/pkg/tsdb/influxdb/influxql/converter/converter.go @@ -1,6 +1,7 @@ package converter import ( + "errors" "fmt" "strconv" "strings" @@ -39,7 +40,7 @@ l1Fields: if err != nil { rsp.Error = err } else { - rsp.Error = fmt.Errorf(v) + rsp.Error = errors.New(v) } return rsp case "code": @@ -55,12 +56,10 @@ l1Fields: } return rspErr(fmt.Errorf("%s", v)) case "": - if err != nil { - return rspErr(err) - } break l1Fields default: v, err := iter.Read() + // TODO: log this properly fmt.Printf("[ROOT] unsupported key: %s / %v\n\n", l1Field, v) if err != nil { if rsp != nil { diff --git a/pkg/tsdb/influxdb/influxql/influxql.go b/pkg/tsdb/influxdb/influxql/influxql.go index 61879579f0e..72053844cbb 100644 --- a/pkg/tsdb/influxdb/influxql/influxql.go +++ b/pkg/tsdb/influxdb/influxql/influxql.go @@ -194,7 +194,7 @@ func execute(ctx context.Context, tracer trace.Tracer, dsInfo *models.Datasource resp = buffered.ResponseParse(res.Body, res.StatusCode, query) } - if resp.Frames != nil && len(resp.Frames) > 0 { + if len(resp.Frames) > 0 { resp.Frames[0].Meta.Custom = readCustomMetadata(res) } diff --git a/pkg/tsdb/mssql/sqleng/sql_engine.go b/pkg/tsdb/mssql/sqleng/sql_engine.go index 060e2bcb02e..a05aa3ce5e6 100644 --- a/pkg/tsdb/mssql/sqleng/sql_engine.go +++ b/pkg/tsdb/mssql/sqleng/sql_engine.go @@ -15,11 +15,10 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/data" - "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" - "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" ) // MetaKeyExecutedQueryString is the key where the executed query should get stored @@ -214,7 +213,7 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG if theErr, ok := r.(error); ok { queryResult.dataResponse.Error = theErr } else if theErrString, ok := r.(string); ok { - queryResult.dataResponse.Error = fmt.Errorf(theErrString) + queryResult.dataResponse.Error = errors.New(theErrString) } else { queryResult.dataResponse.Error = fmt.Errorf("unexpected error - %s", e.userError) } diff --git a/pkg/tsdb/mysql/sqleng/sql_engine.go b/pkg/tsdb/mysql/sqleng/sql_engine.go index 455f8e7bd52..43ab9d47911 100644 --- a/pkg/tsdb/mysql/sqleng/sql_engine.go +++ b/pkg/tsdb/mysql/sqleng/sql_engine.go @@ -16,11 +16,10 @@ import ( "github.com/go-sql-driver/mysql" "github.com/grafana/grafana-plugin-sdk-go/backend" - "github.com/grafana/grafana-plugin-sdk-go/data" - "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" - "github.com/grafana/grafana-plugin-sdk-go/backend/gtime" "github.com/grafana/grafana-plugin-sdk-go/backend/log" + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana-plugin-sdk-go/data/sqlutil" ) // MetaKeyExecutedQueryString is the key where the executed query should get stored @@ -224,7 +223,7 @@ func (e *DataSourceHandler) executeQuery(query backend.DataQuery, wg *sync.WaitG if theErr, ok := r.(error); ok { queryResult.dataResponse.Error = theErr } else if theErrString, ok := r.(string); ok { - queryResult.dataResponse.Error = fmt.Errorf(theErrString) + queryResult.dataResponse.Error = errors.New(theErrString) } else { queryResult.dataResponse.Error = fmt.Errorf("unexpected error - %s", e.userError) } diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index 15c7b885ce8..138f0afc62a 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -3079,7 +3079,7 @@ "description": "Policies contains all policy identifiers included in the certificate.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", "type": "array", "items": { - "$ref": "#/definitions/OID" + "type": "string" } }, "PolicyIdentifiers": { @@ -5404,7 +5404,7 @@ "status" ], "properties": { - "error": { + "message": { "type": "string" }, "refId": { @@ -5414,6 +5414,7 @@ "type": "string", "enum": [ "OK", + "WARNING", "ERROR", "PENDING", "UNKNOWN" @@ -5491,7 +5492,7 @@ "type": "object", "title": "NavbarPreference defines model for NavbarPreference.", "properties": { - "bookmarkIds": { + "bookmarkUrls": { "type": "array", "items": { "type": "string" @@ -5542,10 +5543,6 @@ "format": "int64", "title": "NoticeSeverity is a type for the Severity property of a Notice." }, - "OID": { - "type": "object", - "title": "An OID represents an ASN.1 OBJECT IDENTIFIER." - }, "ObjectIdentifier": { "type": "array", "title": "An ObjectIdentifier represents an ASN.1 OBJECT IDENTIFIER.", diff --git a/public/api-merged.json b/public/api-merged.json index bc7800c62b9..2b69a250f74 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -13557,7 +13557,7 @@ "description": "Policies contains all policy identifiers included in the certificate.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", "type": "array", "items": { - "$ref": "#/definitions/OID" + "type": "string" } }, "PolicyIdentifiers": { @@ -17338,10 +17338,6 @@ } } }, - "OID": { - "type": "object", - "title": "An OID represents an ASN.1 OBJECT IDENTIFIER." - }, "ObjectIdentifier": { "type": "array", "title": "An ObjectIdentifier represents an ASN.1 OBJECT IDENTIFIER.", diff --git a/public/openapi3.json b/public/openapi3.json index f703e5e0bbb..ff75d4292b7 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -3614,7 +3614,7 @@ "Policies": { "description": "Policies contains all policy identifiers included in the certificate.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", "items": { - "$ref": "#/components/schemas/OID" + "type": "string" }, "type": "array" }, @@ -7398,10 +7398,6 @@ "title": "OAuth2 is the oauth2 client configuration.", "type": "object" }, - "OID": { - "title": "An OID represents an ASN.1 OBJECT IDENTIFIER.", - "type": "object" - }, "ObjectIdentifier": { "items": { "format": "int64", diff --git a/scripts/drone/variables.star b/scripts/drone/variables.star index b6559555890..6dcc72a7a1b 100644 --- a/scripts/drone/variables.star +++ b/scripts/drone/variables.star @@ -3,7 +3,7 @@ global variables """ grabpl_version = "v3.0.50" -golang_version = "1.22.4" +golang_version = "1.23.0" # nodejs_version should match what's in ".nvmrc", but without the v prefix. nodejs_version = "20.9.0" diff --git a/scripts/go-workspace/go.mod b/scripts/go-workspace/go.mod index 4e238661615..1003029a81f 100644 --- a/scripts/go-workspace/go.mod +++ b/scripts/go-workspace/go.mod @@ -1,5 +1,5 @@ module github.com/grafana/grafana/scripts/go-workspace -go 1.22.4 +go 1.23.0 require golang.org/x/mod v0.20.0 From 9f8e68e9a1d07e2cffae4633409699874ae420ac Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 21 Aug 2024 16:50:32 +0100 Subject: [PATCH 202/229] Docs: Update adhoc filter documentation (#92197) update adhoc filter documentation --- .../dashboards/variables/add-template-variables/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/dashboards/variables/add-template-variables/index.md b/docs/sources/dashboards/variables/add-template-variables/index.md index ee2e77bace2..acb34992bfb 100644 --- a/docs/sources/dashboards/variables/add-template-variables/index.md +++ b/docs/sources/dashboards/variables/add-template-variables/index.md @@ -236,7 +236,7 @@ groupByNode(summarize(movingAverage(apps.$app.$server.counters.requests.count, 5 _Ad hoc filters_ enable you to add key/value filters that are automatically added to all metric queries that use the specified data source. Unlike other variables, you do not use ad hoc filters in queries. Instead, you use ad hoc filters to write filters for existing queries. {{% admonition type="note" %}} -Ad hoc filter variables only work with Prometheus, Loki, InfluxDB, and Elasticsearch data sources. +Not all data sources support ad hoc filters. Examples of those that do include Prometheus, Loki, InfluxDB, and Elasticsearch. {{% /admonition %}} 1. [Enter general options](#enter-general-options). From 6891eb1d35da74b1cc57081c80b7265525e14178 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:51:23 +0100 Subject: [PATCH 203/229] Update dependency knip to v5.27.2 (#92214) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 979a0410b4d..ad42a0c436f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15811,7 +15811,7 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.16.0": +"enhanced-resolve@npm:^5.16.0, enhanced-resolve@npm:^5.17.1": version: 5.17.1 resolution: "enhanced-resolve@npm:5.17.1" dependencies: @@ -21611,12 +21611,13 @@ __metadata: linkType: hard "knip@npm:^5.10.0": - version: 5.27.0 - resolution: "knip@npm:5.27.0" + version: 5.27.2 + resolution: "knip@npm:5.27.2" dependencies: "@nodelib/fs.walk": "npm:1.2.8" "@snyk/github-codeowners": "npm:1.1.0" easy-table: "npm:1.2.0" + enhanced-resolve: "npm:^5.17.1" fast-glob: "npm:^3.3.2" jiti: "npm:^1.21.6" js-yaml: "npm:^4.1.0" @@ -21624,7 +21625,6 @@ __metadata: picocolors: "npm:^1.0.0" picomatch: "npm:^4.0.1" pretty-ms: "npm:^9.0.0" - resolve: "npm:^1.22.8" smol-toml: "npm:^1.1.4" strip-json-comments: "npm:5.0.1" summary: "npm:2.1.0" @@ -21636,7 +21636,7 @@ __metadata: bin: knip: bin/knip.js knip-bun: bin/knip-bun.js - checksum: 10/0b48a4789b9d9a4444bf6914ff2f71f6e5a926219287b170f2919fe2dc0f1bdb5ee11e1e15f511337684ab1883cb9cde8ea6c5226926f54c382072e139dd388b + checksum: 10/65023f970b400e66b6d5edb7262409647399cd0f6706c54dd70f923b44776b954d3c639495a5bd6e2d82ab8c6b700fced8c0d350b34683417417394c5c8815a1 languageName: node linkType: hard From 2d43fdb29b7a42ae4fd2429b8314fd80fd7fa145 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Wed, 21 Aug 2024 12:12:53 -0400 Subject: [PATCH 204/229] Aggregator: Add mutation and validation handlers (#92036) --- go.work.sum | 3 +- pkg/aggregator/apiserver/plugin/admission.go | 127 +++++++ .../apiserver/plugin/admission/admission.go | 78 ++++ .../plugin/admission/admission_test.go | 97 +++++ .../apiserver/plugin/admission/mutation.go | 56 +++ .../plugin/admission/mutation_test.go | 99 +++++ .../apiserver/plugin/admission/scheme.go | 15 + .../apiserver/plugin/admission/validation.go | 36 ++ .../plugin/admission/validation_test.go | 82 ++++ .../apiserver/plugin/admission_test.go | 218 +++++++++++ .../apiserver/plugin/fakes/client.go | 38 ++ pkg/aggregator/apiserver/plugin/fakes/http.go | 13 + .../apiserver/plugin/fakes/plugin_context.go | 16 + pkg/aggregator/apiserver/plugin/handler.go | 10 +- pkg/aggregator/apiserver/plugin/query_test.go | 27 +- pkg/aggregator/go.mod | 2 + pkg/aggregator/go.sum | 353 ++++++++++++++++++ 17 files changed, 1243 insertions(+), 27 deletions(-) create mode 100644 pkg/aggregator/apiserver/plugin/admission.go create mode 100644 pkg/aggregator/apiserver/plugin/admission/admission.go create mode 100644 pkg/aggregator/apiserver/plugin/admission/admission_test.go create mode 100644 pkg/aggregator/apiserver/plugin/admission/mutation.go create mode 100644 pkg/aggregator/apiserver/plugin/admission/mutation_test.go create mode 100644 pkg/aggregator/apiserver/plugin/admission/scheme.go create mode 100644 pkg/aggregator/apiserver/plugin/admission/validation.go create mode 100644 pkg/aggregator/apiserver/plugin/admission/validation_test.go create mode 100644 pkg/aggregator/apiserver/plugin/admission_test.go create mode 100644 pkg/aggregator/apiserver/plugin/fakes/client.go create mode 100644 pkg/aggregator/apiserver/plugin/fakes/http.go create mode 100644 pkg/aggregator/apiserver/plugin/fakes/plugin_context.go diff --git a/go.work.sum b/go.work.sum index e9b2399c1f8..06919c5662f 100644 --- a/go.work.sum +++ b/go.work.sum @@ -323,7 +323,7 @@ github.com/elastic/go-sysinfo v1.11.2 h1:mcm4OSYVMyws6+n2HIVMGkln5HOpo5Ie1ZmbbNn github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ= 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/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/expr-lang/expr v1.16.2 h1:JvMnzUs3LeVHBvGFcXYmXo+Q6DPDmzrlcSBO6Wy3w4s= github.com/expr-lang/expr v1.16.2/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= @@ -451,6 +451,7 @@ github.com/jaegertracing/jaeger v1.55.0/go.mod h1:S884Mz8H+iGI8Ealq6sM9QzSOeU6P+ github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jedib0t/go-pretty/v6 v6.2.4 h1:wdaj2KHD2W+mz8JgJ/Q6L/T5dB7kyqEFI16eLq7GEmk= github.com/jedib0t/go-pretty/v6 v6.2.4/go.mod h1:+nE9fyyHGil+PuISTCrp7avEdo6bqoMwqZnuiK2r2a0= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jhump/gopoet v0.1.0 h1:gYjOPnzHd2nzB37xYQZxj4EIQNpBrBskRqQQ3q4ZgSg= github.com/jhump/goprotoc v0.5.0 h1:Y1UgUX+txUznfqcGdDef8ZOVlyQvnV0pKWZH08RmZuo= github.com/jmattheis/goverter v1.4.0 h1:SrboBYMpGkj1XSgFhWwqzdP024zIa1+58YzUm+0jcBE= diff --git a/pkg/aggregator/apiserver/plugin/admission.go b/pkg/aggregator/apiserver/plugin/admission.go new file mode 100644 index 00000000000..344e032280e --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/admission.go @@ -0,0 +1,127 @@ +package plugin + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/aggregator/apiserver/plugin/admission" + "github.com/grafana/grafana/pkg/aggregator/apiserver/util" + grafanasemconv "github.com/grafana/grafana/pkg/semconv" + "k8s.io/component-base/tracing" + "k8s.io/klog/v2" +) + +func (h *PluginHandler) AdmissionMutationHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + span := tracing.SpanFromContext(ctx) + span.AddEvent("AdmissionMutationHandler") + responder := &util.Responder{ResponseWriter: w} + ar, err := admission.ParseRequest(h.admissionCodecs, r) + if err != nil { + responder.Error(w, r, err) + return + } + + span.AddEvent("GetPluginContext", + grafanasemconv.GrafanaPluginId(h.dataplaneService.Spec.PluginID), + ) + pluginContext, err := h.pluginContextProvider.GetPluginContext(ctx, h.dataplaneService.Spec.PluginID, "") + if err != nil { + responder.Error(w, r, fmt.Errorf("unable to get plugin context: %w", err)) + return + } + + req, err := admission.ToAdmissionRequest(pluginContext, ar) + if err != nil { + responder.Error(w, r, fmt.Errorf("unable to convert admission request: %w", err)) + return + } + + ctx = backend.WithGrafanaConfig(ctx, pluginContext.GrafanaConfig) + span.AddEvent("MutateAdmission start") + rsp, err := h.client.MutateAdmission(ctx, req) + if err != nil { + responder.Error(w, r, err) + return + } + span.AddEvent("MutateAdmission end") + + span.AddEvent("FromMutationResponse start") + res, err := admission.FromMutationResponse(ar.Request.Object.Raw, rsp) + if err != nil { + responder.Error(w, r, err) + return + } + res.SetGroupVersionKind(ar.GroupVersionKind()) + res.Response.UID = ar.Request.UID + + respBytes, err := json.Marshal(res) + if err != nil { + klog.Error(err) + responder.Error(w, r, err) + return + } + + w.Header().Set("Content-Type", "application/json") + if _, err := w.Write(respBytes); err != nil { + klog.Error(err) + } + }) +} + +func (h *PluginHandler) AdmissionValidationHandler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + span := tracing.SpanFromContext(ctx) + span.AddEvent("AdmissionValidationHandler") + responder := &util.Responder{ResponseWriter: w} + ar, err := admission.ParseRequest(h.admissionCodecs, r) + if err != nil { + responder.Error(w, r, err) + return + } + + span.AddEvent("GetPluginContext", + grafanasemconv.GrafanaPluginId(h.dataplaneService.Spec.PluginID), + ) + pluginContext, err := h.pluginContextProvider.GetPluginContext(ctx, h.dataplaneService.Spec.PluginID, "") + if err != nil { + responder.Error(w, r, fmt.Errorf("unable to get plugin context: %w", err)) + return + } + + req, err := admission.ToAdmissionRequest(pluginContext, ar) + if err != nil { + responder.Error(w, r, fmt.Errorf("unable to convert admission request: %w", err)) + return + } + + ctx = backend.WithGrafanaConfig(ctx, pluginContext.GrafanaConfig) + span.AddEvent("ValidateAdmission start") + rsp, err := h.client.ValidateAdmission(ctx, req) + if err != nil { + responder.Error(w, r, err) + return + } + span.AddEvent("ValidateAdmission end") + + res := admission.FromValidationResponse(rsp) + res.SetGroupVersionKind(ar.GroupVersionKind()) + res.Response.UID = ar.Request.UID + + respBytes, err := json.Marshal(res) + if err != nil { + klog.Error(err) + responder.Error(w, r, err) + return + } + + w.Header().Set("Content-Type", "application/json") + if _, err := w.Write(respBytes); err != nil { + klog.Error(err) + } + }) +} diff --git a/pkg/aggregator/apiserver/plugin/admission/admission.go b/pkg/aggregator/apiserver/plugin/admission/admission.go new file mode 100644 index 00000000000..f62be4492ce --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/admission/admission.go @@ -0,0 +1,78 @@ +package admission + +import ( + "errors" + "fmt" + "io" + "net/http" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/runtime/serializer" +) + +func ToAdmissionRequest(pluginCtx backend.PluginContext, a *admissionv1.AdmissionReview) (*backend.AdmissionRequest, error) { + if a.Request == nil { + return nil, errors.New("admission review request is nil") + } + op, err := ToAdmissionOperation(a.Request.Operation) + if err != nil { + return nil, err + } + + return &backend.AdmissionRequest{ + PluginContext: pluginCtx, + Operation: op, + Kind: backend.GroupVersionKind{ + Group: a.Request.Kind.Group, + Version: a.Request.Kind.Version, + Kind: a.Request.Kind.Kind, + }, + ObjectBytes: a.Request.Object.Raw, + OldObjectBytes: a.Request.OldObject.Raw, + }, nil +} + +func ToAdmissionOperation(o admissionv1.Operation) (backend.AdmissionRequestOperation, error) { + switch o { + case admissionv1.Create: + return backend.AdmissionRequestCreate, nil + case admissionv1.Delete: + return backend.AdmissionRequestDelete, nil + case admissionv1.Update: + return backend.AdmissionRequestUpdate, nil + case admissionv1.Connect: + // TODO: CONNECT is missing from the plugin SDK + return 3, nil + } + return 0, errors.New("unknown admission review operation") +} + +func ParseRequest(codecs serializer.CodecFactory, r *http.Request) (*admissionv1.AdmissionReview, error) { + var body []byte + if r.Body != nil { + if data, err := io.ReadAll(r.Body); err == nil { + body = data + } + } + + contentType := r.Header.Get("Content-Type") + if contentType != "application/json" { + return nil, fmt.Errorf("contentType=%s, expect application/json", contentType) + } + + deserializer := codecs.UniversalDeserializer() + obj, gvk, err := deserializer.Decode(body, nil, nil) + if err != nil { + return nil, fmt.Errorf("failed to decode request: %v", err) + } + + ar, ok := obj.(*admissionv1.AdmissionReview) + if !ok { + return nil, fmt.Errorf("expected AdmissionReview v1, got %T", obj) + } + + ar.SetGroupVersionKind(*gvk) + + return ar, nil +} diff --git a/pkg/aggregator/apiserver/plugin/admission/admission_test.go b/pkg/aggregator/apiserver/plugin/admission/admission_test.go new file mode 100644 index 00000000000..b1017b3e68e --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/admission/admission_test.go @@ -0,0 +1,97 @@ +package admission_test + +import ( + "bytes" + "encoding/json" + "net/http" + "testing" + + admissionv1 "k8s.io/api/admission/v1" + v1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + example "k8s.io/apiserver/pkg/apis/example/v1" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/aggregator/apiserver/plugin/admission" + "github.com/stretchr/testify/require" +) + +func TestParseRequest(t *testing.T) { + exampleObj := example.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "example", + }, + Spec: example.PodSpec{ + ServiceAccountName: "example", + }, + } + + raw, err := json.Marshal(exampleObj) + require.NoError(t, err) + + expectedAR := &admissionv1.AdmissionReview{ + TypeMeta: metav1.TypeMeta{ + Kind: "AdmissionReview", + APIVersion: admissionv1.SchemeGroupVersion.String(), + }, + Request: &admissionv1.AdmissionRequest{ + UID: "1234", + Kind: metav1.GroupVersionKind{Group: "example.k8s.io", Version: "v1", Kind: "Pod"}, + Resource: metav1.GroupVersionResource{Group: "example.k8s.io", Version: "v1", Resource: "pods"}, + Operation: admissionv1.Create, + UserInfo: v1.UserInfo{}, + Object: runtime.RawExtension{Raw: raw}, + OldObject: runtime.RawExtension{}, + DryRun: new(bool), + }, + } + + body, err := json.Marshal(expectedAR) + require.NoError(t, err) + + t.Run("should parse request", func(t *testing.T) { + req, err := http.NewRequest("POST", "/admission", bytes.NewBuffer(body)) + require.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + ar, err := admission.ParseRequest(admission.GetCodecs(), req) + if err != nil { + t.Fatalf("failed to parse request: %v", err) + } + + require.Equal(t, expectedAR, ar) + }) +} + +func TestToAdmissionRequest(t *testing.T) { + pluginCtx := backend.PluginContext{} + admissionReview := &admissionv1.AdmissionReview{ + Request: &admissionv1.AdmissionRequest{ + Operation: admissionv1.Update, + Kind: metav1.GroupVersionKind{ + Group: "example.k8s.io", + Version: "v1", + Kind: "Pod", + }, + Object: runtime.RawExtension{ + Raw: []byte(`{"foo":"bar"}`), + }, + OldObject: runtime.RawExtension{ + Raw: []byte(`{"bar":"foo"}`), + }, + }, + } + + expectedAdmissionRequest := &backend.AdmissionRequest{ + PluginContext: pluginCtx, + Operation: backend.AdmissionRequestUpdate, + Kind: backend.GroupVersionKind{Group: "example.k8s.io", Version: "v1", Kind: "Pod"}, + ObjectBytes: []byte(`{"foo":"bar"}`), + OldObjectBytes: []byte(`{"bar":"foo"}`), + } + + admissionRequest, err := admission.ToAdmissionRequest(pluginCtx, admissionReview) + require.NoError(t, err) + require.Equal(t, expectedAdmissionRequest, admissionRequest) +} diff --git a/pkg/aggregator/apiserver/plugin/admission/mutation.go b/pkg/aggregator/apiserver/plugin/admission/mutation.go new file mode 100644 index 00000000000..07a017e0c29 --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/admission/mutation.go @@ -0,0 +1,56 @@ +package admission + +import ( + "encoding/json" + "errors" + "net/http" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/mattbaird/jsonpatch" + admissionv1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func FromMutationResponse(current []byte, r *backend.MutationResponse) (*admissionv1.AdmissionReview, error) { + res := &admissionv1.AdmissionReview{ + Response: &admissionv1.AdmissionResponse{ + Allowed: r.Allowed, + Warnings: r.Warnings, + }, + } + + if !r.Allowed { + res.Response.Result = &metav1.Status{ + Status: metav1.StatusFailure, + Message: "Internal error", + Reason: metav1.StatusReasonInternalError, + Code: http.StatusInternalServerError, + } + if r.Result != nil { + res.Response.Result.Message = r.Result.Message + res.Response.Result.Reason = metav1.StatusReason(r.Result.Reason) + res.Response.Result.Code = r.Result.Code + } + return res, nil + } + + if r.Allowed && len(r.ObjectBytes) == 0 { + return nil, errors.New("empty mutation response object bytes") + } + + patch, err := jsonpatch.CreatePatch(current, r.ObjectBytes) + if err != nil { + return nil, err + } + + raw, err := json.Marshal(patch) + if err != nil { + return nil, err + } + + res.Response.Patch = raw + pt := admissionv1.PatchTypeJSONPatch + res.Response.PatchType = &pt + + return res, nil +} diff --git a/pkg/aggregator/apiserver/plugin/admission/mutation_test.go b/pkg/aggregator/apiserver/plugin/admission/mutation_test.go new file mode 100644 index 00000000000..c89dac4ee0a --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/admission/mutation_test.go @@ -0,0 +1,99 @@ +package admission_test + +import ( + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/aggregator/apiserver/plugin/admission" + "github.com/stretchr/testify/require" + admissionv1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestFromMutationResponse(t *testing.T) { + warnings := []string{"warning 1", "warning 2"} + + exampleObj := []byte(`{"key": "value"}`) + + result := &backend.StatusResult{ + Status: "Failure", + Message: "message", + Reason: "reason", + Code: 200, + } + + t.Run("should return expected patch", func(t *testing.T) { + response := &backend.MutationResponse{ + Allowed: true, + Warnings: warnings, + ObjectBytes: []byte(`{"key": "value2"}`), + } + pt := admissionv1.PatchTypeJSONPatch + expectedAdmissionResponse := &admissionv1.AdmissionResponse{ + Allowed: true, + Warnings: warnings, + Patch: []byte(`[{"op":"replace","path":"/key","value":"value2"}]`), + PatchType: &pt, + } + expectedAdmissionReview := &admissionv1.AdmissionReview{ + Response: expectedAdmissionResponse, + } + actualAdmissionReview, err := admission.FromMutationResponse(exampleObj, response) + require.NoError(t, err) + require.Equal(t, expectedAdmissionReview, actualAdmissionReview) + }) + + t.Run("should error if MutationResponse has empty ObjectBytes", func(t *testing.T) { + response := &backend.MutationResponse{ + Allowed: true, + Warnings: warnings, + } + _, err := admission.FromMutationResponse(exampleObj, response) + require.Error(t, err) + }) + + t.Run("should include Result in AdmissionResponse when not allowed", func(t *testing.T) { + response := &backend.MutationResponse{ + Allowed: false, + Warnings: warnings, + Result: result, + } + expectedAdmissionResponse := &admissionv1.AdmissionResponse{ + Allowed: false, + Warnings: warnings, + Result: &metav1.Status{ + Status: result.Status, + Message: result.Message, + Reason: metav1.StatusReason(result.Reason), + Code: result.Code, + }, + } + expectedAdmissionReview := &admissionv1.AdmissionReview{ + Response: expectedAdmissionResponse, + } + actualAdmissionReview, err := admission.FromMutationResponse(exampleObj, response) + require.NoError(t, err) + require.Equal(t, expectedAdmissionReview, actualAdmissionReview) + }) + + t.Run("should handle nil Warnings and Result", func(t *testing.T) { + response := &backend.MutationResponse{ + Allowed: false, + } + expectedAdmissionResponse := &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Status: metav1.StatusFailure, + Message: "Internal error", + Reason: metav1.StatusReasonInternalError, + Code: 500, + }, + } + expectedAdmissionReview := &admissionv1.AdmissionReview{ + Response: expectedAdmissionResponse, + } + actualAdmissionReview, err := admission.FromMutationResponse(exampleObj, response) + require.NoError(t, err) + require.Equal(t, expectedAdmissionReview, actualAdmissionReview) + }) +} diff --git a/pkg/aggregator/apiserver/plugin/admission/scheme.go b/pkg/aggregator/apiserver/plugin/admission/scheme.go new file mode 100644 index 00000000000..2738347fcdf --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/admission/scheme.go @@ -0,0 +1,15 @@ +package admission + +import ( + admissionv1 "k8s.io/api/admission/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" +) + +func GetCodecs() serializer.CodecFactory { + scheme := runtime.NewScheme() + codecs := serializer.NewCodecFactory(scheme) + utilruntime.Must(admissionv1.AddToScheme(scheme)) + return codecs +} diff --git a/pkg/aggregator/apiserver/plugin/admission/validation.go b/pkg/aggregator/apiserver/plugin/admission/validation.go new file mode 100644 index 00000000000..6f181710bed --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/admission/validation.go @@ -0,0 +1,36 @@ +package admission + +import ( + "net/http" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + admissionv1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func FromValidationResponse(r *backend.ValidationResponse) *admissionv1.AdmissionReview { + res := &admissionv1.AdmissionResponse{ + Allowed: r.Allowed, + Warnings: r.Warnings, + } + + if !r.Allowed { + res.Result = &metav1.Status{ + Status: metav1.StatusFailure, + Message: "Internal error", + Reason: metav1.StatusReasonInternalError, + Code: http.StatusInternalServerError, + } + if r.Result != nil { + res.Result.Message = r.Result.Message + res.Result.Reason = metav1.StatusReason(r.Result.Reason) + res.Result.Code = r.Result.Code + } + } + + resAR := &admissionv1.AdmissionReview{ + Response: res, + } + + return resAR +} diff --git a/pkg/aggregator/apiserver/plugin/admission/validation_test.go b/pkg/aggregator/apiserver/plugin/admission/validation_test.go new file mode 100644 index 00000000000..8e5228262a4 --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/admission/validation_test.go @@ -0,0 +1,82 @@ +package admission_test + +import ( + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/aggregator/apiserver/plugin/admission" + "github.com/stretchr/testify/require" + admissionv1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestFromValidationResponse(t *testing.T) { + warnings := []string{"warning 1", "warning 2"} + + result := &backend.StatusResult{ + Status: "Failure", + Message: "message", + Reason: "reason", + Code: 200, + } + + t.Run("should include Result in AdmissionResponse when not allowed", func(t *testing.T) { + response := &backend.ValidationResponse{ + Allowed: false, + Warnings: warnings, + Result: result, + } + expectedAdmissionResponse := &admissionv1.AdmissionResponse{ + Allowed: false, + Warnings: warnings, + Result: &metav1.Status{ + Status: result.Status, + Message: result.Message, + Reason: metav1.StatusReason(result.Reason), + Code: result.Code, + }, + } + expectedAdmissionReview := &admissionv1.AdmissionReview{ + Response: expectedAdmissionResponse, + } + actualAdmissionReview := admission.FromValidationResponse(response) + require.Equal(t, expectedAdmissionReview, actualAdmissionReview) + }) + + t.Run("should not include Result in AdmissionResponse when allowed", func(t *testing.T) { + response := &backend.ValidationResponse{ + Allowed: true, + Warnings: warnings, + Result: result, + } + expectedAdmissionResponse := &admissionv1.AdmissionResponse{ + Allowed: true, + Warnings: warnings, + } + expectedAdmissionReview := &admissionv1.AdmissionReview{ + Response: expectedAdmissionResponse, + } + actualAdmissionReview := admission.FromValidationResponse(response) + require.Equal(t, expectedAdmissionReview, actualAdmissionReview) + }) + + t.Run("should handle nil Warnings and Result", func(t *testing.T) { + response := &backend.ValidationResponse{ + Allowed: false, + } + expectedAdmissionResponse := &admissionv1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Status: metav1.StatusFailure, + Message: "Internal error", + Reason: metav1.StatusReasonInternalError, + Code: 500, + }, + } + expectedAdmissionReview := &admissionv1.AdmissionReview{ + Response: expectedAdmissionResponse, + } + actualAdmissionReview := admission.FromValidationResponse(response) + require.Equal(t, expectedAdmissionReview, actualAdmissionReview) + }) +} diff --git a/pkg/aggregator/apiserver/plugin/admission_test.go b/pkg/aggregator/apiserver/plugin/admission_test.go new file mode 100644 index 00000000000..a90aca0f29a --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/admission_test.go @@ -0,0 +1,218 @@ +package plugin + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + admissionv1 "k8s.io/api/admission/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/grafana/grafana/pkg/aggregator/apis/aggregation/v0alpha1" + "github.com/grafana/grafana/pkg/aggregator/apiserver/plugin/fakes" +) + +func TestAdmissionMutation(t *testing.T) { + dps := v0alpha1.DataPlaneService{ + Spec: v0alpha1.DataPlaneServiceSpec{ + PluginID: "testds", + Group: "testds.example.com", + Version: "v1", + Services: []v0alpha1.Service{ + { + Type: v0alpha1.AdmissionControlServiceType, + }, + }, + }, + } + + pluginContext := backend.PluginContext{ + DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{ + ID: 1, + }, + } + contextProvider := &fakes.FakePluginContextProvider{ + PluginContext: pluginContext, + } + + admissionReview := &admissionv1.AdmissionReview{ + TypeMeta: metav1.TypeMeta{ + Kind: "AdmissionReview", + APIVersion: admissionv1.SchemeGroupVersion.String(), + }, + Request: &admissionv1.AdmissionRequest{ + UID: "1234", + Operation: admissionv1.Update, + Kind: metav1.GroupVersionKind{ + Group: "example.k8s.io", + Version: "v1", + Kind: "Pod", + }, + Object: runtime.RawExtension{ + Raw: []byte(`{"foo":"bar"}`), + }, + OldObject: runtime.RawExtension{ + Raw: []byte(`{"bar":"foo"}`), + }, + }, + } + + pluginRes := &backend.MutationResponse{ + Allowed: true, + ObjectBytes: []byte(`{"foo": "foo"}`), + } + + jsonAdmissionReview, err := json.Marshal(admissionReview) + require.NoError(t, err) + + pc := &fakes.FakePluginClient{ + MutateAdmissionFunc: newFakeMutateAdmissionHandler(pluginRes, nil), + } + + delegate := fakes.NewFakeHTTPHandler(http.StatusNotFound, []byte(`Not Found`)) + handler := NewPluginHandler(pc, dps, contextProvider, delegate) + + t.Run("should return mutation response", func(t *testing.T) { + req, err := http.NewRequest("POST", "/apis/testds.example.com/v1/admission/mutate", bytes.NewBuffer(jsonAdmissionReview)) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + pt := admissionv1.PatchTypeJSONPatch + expectedRes := &admissionv1.AdmissionReview{ + TypeMeta: metav1.TypeMeta{ + Kind: "AdmissionReview", + APIVersion: admissionv1.SchemeGroupVersion.String(), + }, + Response: &admissionv1.AdmissionResponse{ + UID: admissionReview.Request.UID, + Allowed: true, + Patch: []byte(`[{"op":"replace","path":"/foo","value":"foo"}]`), + PatchType: &pt, + }, + } + actualRes := &admissionv1.AdmissionReview{} + assert.NoError(t, json.NewDecoder(rr.Body).Decode(actualRes)) + require.Equal(t, expectedRes, actualRes) + }) +} + +func TestAdmissionValidation(t *testing.T) { + dps := v0alpha1.DataPlaneService{ + Spec: v0alpha1.DataPlaneServiceSpec{ + PluginID: "testds", + Group: "testds.example.com", + Version: "v1", + Services: []v0alpha1.Service{ + { + Type: v0alpha1.AdmissionControlServiceType, + }, + }, + }, + } + + pluginContext := backend.PluginContext{ + DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{ + ID: 1, + }, + } + contextProvider := &fakes.FakePluginContextProvider{ + PluginContext: pluginContext, + } + + admissionReview := &admissionv1.AdmissionReview{ + TypeMeta: metav1.TypeMeta{ + Kind: "AdmissionReview", + APIVersion: admissionv1.SchemeGroupVersion.String(), + }, + Request: &admissionv1.AdmissionRequest{ + UID: "1234", + Operation: admissionv1.Update, + Kind: metav1.GroupVersionKind{ + Group: "example.k8s.io", + Version: "v1", + Kind: "Pod", + }, + Object: runtime.RawExtension{ + Raw: []byte(`{"foo":"bar"}`), + }, + OldObject: runtime.RawExtension{ + Raw: []byte(`{"bar":"foo"}`), + }, + }, + } + + pluginRes := &backend.ValidationResponse{ + Allowed: false, + Result: &backend.StatusResult{ + Status: "Failure", + Message: "message", + Reason: "NotFound", + Code: 404, + }, + Warnings: []string{"warning 1", "warning 2"}, + } + + jsonAdmissionReview, err := json.Marshal(admissionReview) + require.NoError(t, err) + + pc := &fakes.FakePluginClient{ + ValidateAdmissionFunc: newFakeValidateAdmissionHandler(pluginRes, nil), + } + + delegate := fakes.NewFakeHTTPHandler(http.StatusNotFound, []byte(`Not Found`)) + handler := NewPluginHandler(pc, dps, contextProvider, delegate) + + t.Run("should return validation response", func(t *testing.T) { + req, err := http.NewRequest("POST", "/apis/testds.example.com/v1/admission/validate", bytes.NewBuffer(jsonAdmissionReview)) + assert.NoError(t, err) + req.Header.Set("Content-Type", "application/json") + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + + assert.Equal(t, http.StatusOK, rr.Code) + expectedRes := &admissionv1.AdmissionReview{ + TypeMeta: metav1.TypeMeta{ + Kind: "AdmissionReview", + APIVersion: admissionv1.SchemeGroupVersion.String(), + }, + Response: &admissionv1.AdmissionResponse{ + UID: admissionReview.Request.UID, + Allowed: false, + Result: &metav1.Status{ + Status: metav1.StatusFailure, + Message: "message", + Reason: metav1.StatusReasonNotFound, + Code: 404, + }, + Warnings: pluginRes.Warnings, + }, + } + actualRes := &admissionv1.AdmissionReview{} + assert.NoError(t, json.NewDecoder(rr.Body).Decode(actualRes)) + require.Equal(t, expectedRes, actualRes) + }) +} + +func newFakeMutateAdmissionHandler(response *backend.MutationResponse, err error) backend.MutateAdmissionFunc { + return func(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) { + return response, err + } +} + +func newFakeValidateAdmissionHandler(response *backend.ValidationResponse, err error) backend.ValidateAdmissionFunc { + return func(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) { + return response, err + } +} diff --git a/pkg/aggregator/apiserver/plugin/fakes/client.go b/pkg/aggregator/apiserver/plugin/fakes/client.go new file mode 100644 index 00000000000..2b177ca4c7f --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/fakes/client.go @@ -0,0 +1,38 @@ +package fakes + +import ( + "context" + "errors" + + "github.com/grafana/grafana-plugin-sdk-go/backend" +) + +type FakePluginClient struct { + backend.QueryDataHandlerFunc + backend.MutateAdmissionFunc + backend.ValidateAdmissionFunc +} + +func (pc *FakePluginClient) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { + if pc.QueryDataHandlerFunc != nil { + return pc.QueryDataHandlerFunc(ctx, req) + } + + return nil, errors.New("QueryDataHandlerFunc not implemented") +} + +func (pc *FakePluginClient) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) { + if pc.ValidateAdmissionFunc != nil { + return pc.ValidateAdmissionFunc(ctx, req) + } + + return nil, errors.New("ValidateAdmissionFunc not implemented") +} + +func (pc *FakePluginClient) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) { + if pc.MutateAdmissionFunc != nil { + return pc.MutateAdmissionFunc(ctx, req) + } + + return nil, errors.New("MutateAdmissionFunc not implemented") +} diff --git a/pkg/aggregator/apiserver/plugin/fakes/http.go b/pkg/aggregator/apiserver/plugin/fakes/http.go new file mode 100644 index 00000000000..64961113da6 --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/fakes/http.go @@ -0,0 +1,13 @@ +package fakes + +import "net/http" + +func NewFakeHTTPHandler(status int, res []byte) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(status) + _, err := w.Write(res) + if err != nil { + panic(err) + } + } +} diff --git a/pkg/aggregator/apiserver/plugin/fakes/plugin_context.go b/pkg/aggregator/apiserver/plugin/fakes/plugin_context.go new file mode 100644 index 00000000000..530e2b774da --- /dev/null +++ b/pkg/aggregator/apiserver/plugin/fakes/plugin_context.go @@ -0,0 +1,16 @@ +package fakes + +import ( + "context" + + "github.com/grafana/grafana-plugin-sdk-go/backend" +) + +type FakePluginContextProvider struct { + PluginContext backend.PluginContext + Err error +} + +func (f FakePluginContextProvider) GetPluginContext(ctx context.Context, pluginID, dsUID string) (backend.PluginContext, error) { + return f.PluginContext, f.Err +} diff --git a/pkg/aggregator/apiserver/plugin/handler.go b/pkg/aggregator/apiserver/plugin/handler.go index 162d73bfce2..cd09bf45efb 100644 --- a/pkg/aggregator/apiserver/plugin/handler.go +++ b/pkg/aggregator/apiserver/plugin/handler.go @@ -6,15 +6,15 @@ import ( "path" "github.com/grafana/grafana-plugin-sdk-go/backend" + "k8s.io/apimachinery/pkg/runtime/serializer" aggregationv0alpha1 "github.com/grafana/grafana/pkg/aggregator/apis/aggregation/v0alpha1" + "github.com/grafana/grafana/pkg/aggregator/apiserver/plugin/admission" ) type PluginClient interface { backend.QueryDataHandler - backend.StreamHandler backend.AdmissionHandler - backend.CallResourceHandler } type PluginContextProvider interface { @@ -29,6 +29,8 @@ type PluginHandler struct { pluginContextProvider PluginContextProvider dataplaneService aggregationv0alpha1.DataPlaneService + + admissionCodecs serializer.CodecFactory } func NewPluginHandler( @@ -43,6 +45,7 @@ func NewPluginHandler( client: client, pluginContextProvider: pluginContextProvider, dataplaneService: dataplaneService, + admissionCodecs: admission.GetCodecs(), } h.registerRoutes() return h @@ -54,7 +57,8 @@ func (h *PluginHandler) registerRoutes() { for _, service := range h.dataplaneService.Spec.Services { switch service.Type { case aggregationv0alpha1.AdmissionControlServiceType: - // TODO: implement in future PR + h.mux.Handle(proxyPath("/admission/mutate"), h.AdmissionMutationHandler()) + h.mux.Handle(proxyPath("/admission/validate"), h.AdmissionValidationHandler()) case aggregationv0alpha1.ConversionServiceType: // TODO: implement in future PR case aggregationv0alpha1.DataSourceProxyServiceType: diff --git a/pkg/aggregator/apiserver/plugin/query_test.go b/pkg/aggregator/apiserver/plugin/query_test.go index 7b28c7b4214..97bfabb3a08 100644 --- a/pkg/aggregator/apiserver/plugin/query_test.go +++ b/pkg/aggregator/apiserver/plugin/query_test.go @@ -14,7 +14,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/data" datav0alpha1 "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1" "github.com/grafana/grafana/pkg/aggregator/apis/aggregation/v0alpha1" - "github.com/grafana/grafana/pkg/plugins/manager/fakes" + "github.com/grafana/grafana/pkg/aggregator/apiserver/plugin/fakes" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -38,8 +38,8 @@ func TestQueryDataHandler(t *testing.T) { ID: 1, }, } - contextProvider := &fakePluginContextProvider{ - pluginContext: pluginContext, + contextProvider := &fakes.FakePluginContextProvider{ + PluginContext: pluginContext, } res := &backend.QueryDataResponse{ @@ -58,7 +58,7 @@ func TestQueryDataHandler(t *testing.T) { QueryDataHandlerFunc: newfakeQueryDataHandler(res, nil), } - delegate := newFakeHTTPHandler(http.StatusNotFound, []byte(`Not Found`)) + delegate := fakes.NewFakeHTTPHandler(http.StatusNotFound, []byte(`Not Found`)) handler := NewPluginHandler(pc, dps, contextProvider, delegate) qdr := datav0alpha1.QueryDataRequest{ @@ -176,27 +176,8 @@ func TestQueryDataHandler(t *testing.T) { }) } -type fakePluginContextProvider struct { - pluginContext backend.PluginContext - err error -} - -func (f fakePluginContextProvider) GetPluginContext(ctx context.Context, pluginID, dsUID string) (backend.PluginContext, error) { - return f.pluginContext, f.err -} - func newfakeQueryDataHandler(res *backend.QueryDataResponse, err error) backend.QueryDataHandlerFunc { return func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) { return res, err } } - -func newFakeHTTPHandler(status int, res []byte) http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - w.WriteHeader(status) - _, err := w.Write(res) - if err != nil { - panic(err) - } - } -} diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index ca4483da864..fdf3f050b1c 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -7,6 +7,7 @@ require ( github.com/grafana/grafana-plugin-sdk-go v0.244.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 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.28.0 k8s.io/api v0.31.0 @@ -36,6 +37,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/fatih/color v1.16.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index 77559cf6a89..0223817b123 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -1,182 +1,535 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE= +github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/genny v1.0.0 h1:uGGa4nei+j20rOSeDeP5Of12XVm7TGUd4dJA9RDitfE= +github.com/cheekybits/genny v1.0.0/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071 h1:RdCf9hH3xq5vJifrjGB7zQlFkdRB3pAppcX2helDq2U= +github.com/chromedp/cdproto v0.0.0-20240426225625-909263490071/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/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 v0.0.0-20231117061959-7cc037d33fb5 h1:m62nsMU279qRD9PQSWD1l66kmkXzuYcnVJqL4XLeV2M= +github.com/elazarl/goproxy v0.0.0-20231117061959-7cc037d33fb5/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac h1:9yrT5tmn9Zc0ytWPASlaPwQfQMQYnRf0RSDe1XvHw0Q= +github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= 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= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/getkin/kin-openapi v0.125.0 h1:jyQCyf2qXS1qvs2U00xQzkGCqYPhEhZDmSmVt65fXno= +github.com/getkin/kin-openapi v0.125.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= +github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= +github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= 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.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/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= github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= 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/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 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.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/grafana-plugin-sdk-go v0.244.0 h1:ZZxHbiiF6QcsnlbPFyZGmzNDoTC1pLeHXUQYoskWt5c= +github.com/grafana/grafana-plugin-sdk-go v0.244.0/go.mod h1:H3FXrJMUlwocQ6UYj8Ds5I9EzRAVOcdRcgaRE3mXQqk= 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= +github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435/go.mod h1:8cz+z0i57IjN6MYmu/zZQdCg9CQcsnEHbaJBBEf3KQo= 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= +github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= +github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ= +github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 h1:hQWBtNqRYrI7CWIaUSXXtNKR90KzcUA5uiuxFVWw7sU= +github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= +github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= +github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= +github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= +github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4= github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= +github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= +github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a h1:vcrhXnj9g9PIE+cmZgaPSwOyJ8MAQTRmsgGrB0x5rF4= +github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.15 h1:nuqt+pdC/KqswQKhETJjo7pvn/k4xMUxgW6liI7XpnM= +github.com/urfave/cli v1.22.15/go.mod h1:wSan1hmo5zeyLGBjRJbzRTNk8gwoYa2B9n4q9dmRIc0= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= +github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= +go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= +go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= go.etcd.io/etcd/client/v2 v2.305.13 h1:RWfV1SX5jTU0lbCvpVQe3iPQeAHETWdOTb6pxhd77C8= +go.etcd.io/etcd/client/v2 v2.305.13/go.mod h1:iQnL7fepbiomdXMb3om1rHq96htNNGv2sJkEcZGDRRg= go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= +go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= go.etcd.io/etcd/pkg/v3 v3.5.13 h1:st9bDWNsKkBNpP4PR1MvM/9NqUPfvYZx/YXegsYEH8M= +go.etcd.io/etcd/pkg/v3 v3.5.13/go.mod h1:N+4PLrp7agI/Viy+dUYpX7iRtSPvKq+w8Y14d1vX+m0= go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= +go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= +go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.53.0 h1:IVtyPth4Rs5P8wIf0mP2KVKFNTJ4paX9qQ4Hkh5gFdc= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.53.0/go.mod h1:ImRBLMJv177/pwiLZ7tU7HDGNdBv7rS0HQ99eN/zBl8= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= go.opentelemetry.io/contrib/propagators/jaeger v1.28.0 h1:xQ3ktSVS128JWIaN1DiPGIjcH+GsvkibIAVRWFjS9eM= +go.opentelemetry.io/contrib/propagators/jaeger v1.28.0/go.mod h1:O9HIyI2kVBrFoEwQZ0IN6PHXykGoit4mZV2aEjkTRH4= go.opentelemetry.io/contrib/samplers/jaegerremote v0.22.0 h1:OYxqumWcd1yaV/qvCt1B7Sru9OeUNGjeXq/oldx3AGk= +go.opentelemetry.io/contrib/samplers/jaegerremote v0.22.0/go.mod h1:2tZTRqCbvx7nG57wUwd5NQpNVujOWnR84iPLllIH0Ok= +go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= +go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= +gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= +google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +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= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= +gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= +k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= +k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/apiserver v0.31.0 h1:p+2dgJjy+bk+B1Csz+mc2wl5gHwvNkC9QJV+w55LVrY= +k8s.io/apiserver v0.31.0/go.mod h1:KI9ox5Yu902iBnnyMmy7ajonhKnkeZYJhTZ/YI+WEMk= k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= +k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs= +k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= From 71f0dae39e9d432584cffdc803f869bb3bb8c1d3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:57:09 +0000 Subject: [PATCH 205/229] Update dependency mini-css-extract-plugin to v2.9.1 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 563c24a39b4..73e4fdee5a9 100644 --- a/package.json +++ b/package.json @@ -202,7 +202,7 @@ "jest-watch-typeahead": "^2.2.2", "knip": "^5.10.0", "lerna": "8.1.8", - "mini-css-extract-plugin": "2.9.0", + "mini-css-extract-plugin": "2.9.1", "msw": "2.3.5", "mutationobserver-shim": "0.3.7", "ngtemplate-loader": "2.1.0", diff --git a/yarn.lock b/yarn.lock index ad42a0c436f..088ddc547a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18626,7 +18626,7 @@ __metadata: marked: "npm:12.0.2" memoize-one: "npm:6.0.0" micro-memoize: "npm:^4.1.2" - mini-css-extract-plugin: "npm:2.9.0" + mini-css-extract-plugin: "npm:2.9.1" ml-regression-polynomial: "npm:^3.0.0" ml-regression-simple-linear: "npm:^3.0.0" moment: "npm:2.30.1" @@ -23208,15 +23208,15 @@ __metadata: languageName: node linkType: hard -"mini-css-extract-plugin@npm:2.9.0": - version: 2.9.0 - resolution: "mini-css-extract-plugin@npm:2.9.0" +"mini-css-extract-plugin@npm:2.9.1": + version: 2.9.1 + resolution: "mini-css-extract-plugin@npm:2.9.1" dependencies: schema-utils: "npm:^4.0.0" tapable: "npm:^2.2.1" peerDependencies: webpack: ^5.0.0 - checksum: 10/4c9ee9c0c6160a64a4884d5a92a1a5c0b68d556cd00f975cf6c8a79b51ac90e6130a37b3832b17d377d0cb1b31c0313c8c023458d4f69e95fe3424a8b43d834f + checksum: 10/a4a0c73a054254784b9d39a3a4f117691600355125242dfc46ced0912b4937050823478bdbf403b5392c21e2fb2203902b41677d67c7d668f77b985b594e94c6 languageName: node linkType: hard From 2136fd9a929334f39a371c2127c57b6d83abdf28 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 21 Aug 2024 19:28:30 +0300 Subject: [PATCH 206/229] Storage: Remove unified storage feature flag (#92192) remove unified storage flag --- .../configure-grafana/feature-toggles/index.md | 1 - packages/grafana-data/src/types/featureToggles.gen.ts | 1 - pkg/services/apiserver/service.go | 8 -------- pkg/services/featuremgmt/registry.go | 8 -------- pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 4 ---- pkg/services/featuremgmt/toggles_gen.json | 1 + pkg/services/sqlstore/sqlstore.go | 1 - pkg/storage/unified/sql/db/dbimpl/dbimpl.go | 10 ++++------ pkg/storage/unified/sql/test/integration_test.go | 4 ++-- pkg/tests/apis/playlist/playlist_test.go | 4 ---- 11 files changed, 7 insertions(+), 36 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index ce404a688f2..fb232934816 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -121,7 +121,6 @@ Experimental features might be changed or removed without prior notice. | `canvasPanelNesting` | Allow elements nesting | | `disableSecretsCompatibility` | Disable duplicated secret storage in legacy tables | | `logRequestsInstrumentedAsUnknown` | Logs the path for requests that are instrumented as unknown | -| `unifiedStorage` | SQL-based k8s storage | | `showDashboardValidationWarnings` | Show warnings when dashboards do not validate against the schema | | `mysqlAnsiQuotes` | Use double quotes to escape keyword in a MySQL query | | `alertingBacktesting` | Rule backtesting API for alerting | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 0d26cd877e6..72e6580d6b0 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -42,7 +42,6 @@ export interface FeatureToggles { logRequestsInstrumentedAsUnknown?: boolean; topnav?: boolean; grpcServer?: boolean; - unifiedStorage?: boolean; cloudWatchCrossAccountQuerying?: boolean; showDashboardValidationWarnings?: boolean; mysqlAnsiQuotes?: boolean; diff --git a/pkg/services/apiserver/service.go b/pkg/services/apiserver/service.go index 4c86b38e074..0796758c6c6 100644 --- a/pkg/services/apiserver/service.go +++ b/pkg/services/apiserver/service.go @@ -285,10 +285,6 @@ func (s *service) start(ctx context.Context) error { } case grafanaapiserveroptions.StorageTypeUnified: - if !s.features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorage) { - return fmt.Errorf("unified storage requires the unifiedStorage feature flag") - } - server, err := sql.ProvideResourceServer(s.db, s.cfg, s.features, s.tracing) if err != nil { return err @@ -298,10 +294,6 @@ func (s *service) start(ctx context.Context) error { o.RecommendedOptions.Etcd.StorageConfig) case grafanaapiserveroptions.StorageTypeUnifiedGrpc: - if !s.features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorage) { - return fmt.Errorf("unified storage requires the unifiedStorage feature flag") - } - opts := []grpc.DialOption{ grpc.WithStatsHandler(otelgrpc.NewClientHandler()), grpc.WithTransportCredentials(insecure.NewCredentials()), diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index ca00201458c..991261d8d76 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -194,14 +194,6 @@ var ( Owner: grafanaSearchAndStorageSquad, HideFromAdminPage: true, }, - { - Name: "unifiedStorage", - Description: "SQL-based k8s storage", - Stage: FeatureStageExperimental, - RequiresDevMode: false, - RequiresRestart: true, // new SQL tables created - Owner: grafanaSearchAndStorageSquad, - }, { Name: "cloudWatchCrossAccountQuerying", Description: "Enables cross-account querying in CloudWatch datasources", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 355fa7d25da..15329fe8233 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -23,7 +23,6 @@ disableSecretsCompatibility,experimental,@grafana/hosted-grafana-team,false,true logRequestsInstrumentedAsUnknown,experimental,@grafana/hosted-grafana-team,false,false,false topnav,deprecated,@grafana/grafana-frontend-platform,false,false,false grpcServer,preview,@grafana/search-and-storage,false,false,false -unifiedStorage,experimental,@grafana/search-and-storage,false,true,false cloudWatchCrossAccountQuerying,GA,@grafana/aws-datasources,false,false,false showDashboardValidationWarnings,experimental,@grafana/dashboards-squad,false,false,false mysqlAnsiQuotes,experimental,@grafana/search-and-storage,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 89c2c2c1909..15cae3b0f85 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -103,10 +103,6 @@ const ( // Run the GRPC server FlagGrpcServer = "grpcServer" - // FlagUnifiedStorage - // SQL-based k8s storage - FlagUnifiedStorage = "unifiedStorage" - // FlagCloudWatchCrossAccountQuerying // Enables cross-account querying in CloudWatch datasources FlagCloudWatchCrossAccountQuerying = "cloudWatchCrossAccountQuerying" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index a8a0ac0e195..5c02edfcc2c 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -2695,6 +2695,7 @@ "name": "unifiedStorage", "resourceVersion": "1724096690370", "creationTimestamp": "2023-12-06T20:21:21Z", + "deletionTimestamp": "2024-08-21T09:30:06Z", "annotations": { "grafana.app/updatedTimestamp": "2024-08-19 19:44:50.370023815 +0000 UTC" } diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index 4b17a54e37d..e5c1a178a95 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -481,7 +481,6 @@ func getCfgForTesting(opts ...InitTestDBOpt) *setting.Cfg { func getFeaturesForTesting(opts ...InitTestDBOpt) featuremgmt.FeatureToggles { featureKeys := []any{ featuremgmt.FlagPanelTitleSearch, - featuremgmt.FlagUnifiedStorage, } for _, opt := range opts { if len(opt.FeatureFlags) > 0 { diff --git a/pkg/storage/unified/sql/db/dbimpl/dbimpl.go b/pkg/storage/unified/sql/db/dbimpl/dbimpl.go index af533ef0180..b9498b87cd5 100644 --- a/pkg/storage/unified/sql/db/dbimpl/dbimpl.go +++ b/pkg/storage/unified/sql/db/dbimpl/dbimpl.go @@ -61,12 +61,10 @@ func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, features feat } p = &resourceDBProvider{ - cfg: cfg, - log: log.New("entity-db"), - logQueries: getter.Key("log_queries").MustBool(false), - } - if features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorage) { - p.migrateFunc = migrations.MigrateResourceStore + cfg: cfg, + log: log.New("entity-db"), + logQueries: getter.Key("log_queries").MustBool(false), + migrateFunc: migrations.MigrateResourceStore, } switch dbType := getter.Key("db_type").MustString(""); dbType { diff --git a/pkg/storage/unified/sql/test/integration_test.go b/pkg/storage/unified/sql/test/integration_test.go index d3b6ed37303..7d618d2aab1 100644 --- a/pkg/storage/unified/sql/test/integration_test.go +++ b/pkg/storage/unified/sql/test/integration_test.go @@ -31,7 +31,7 @@ func newServer(t *testing.T) (sql.Backend, resource.ResourceServer) { dbstore := infraDB.InitTestDB(t) cfg := setting.NewCfg() - features := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorage) + features := featuremgmt.WithFeatures() eDB, err := dbimpl.ProvideResourceDB(dbstore, cfg, features, nil) require.NoError(t, err) @@ -331,7 +331,7 @@ func TestClientServer(t *testing.T) { cfg.GRPCServerAddress = "localhost:0" cfg.GRPCServerNetwork = "tcp" - features := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorage) + features := featuremgmt.WithFeatures() svc, err := sql.ProvideService(cfg, features, dbstore, nil) require.NoError(t, err) diff --git a/pkg/tests/apis/playlist/playlist_test.go b/pkg/tests/apis/playlist/playlist_test.go index d42b63d5149..6840ecc946e 100644 --- a/pkg/tests/apis/playlist/playlist_test.go +++ b/pkg/tests/apis/playlist/playlist_test.go @@ -149,7 +149,6 @@ func TestIntegrationPlaylist(t *testing.T) { DisableAnonymous: true, APIServerStorageType: "unified", // use the entity api tables EnableFeatureToggles: []string{ - featuremgmt.FlagUnifiedStorage, featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ @@ -164,7 +163,6 @@ func TestIntegrationPlaylist(t *testing.T) { DisableAnonymous: true, APIServerStorageType: "unified", // use the entity api tables EnableFeatureToggles: []string{ - featuremgmt.FlagUnifiedStorage, featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ @@ -179,7 +177,6 @@ func TestIntegrationPlaylist(t *testing.T) { DisableAnonymous: true, APIServerStorageType: "unified", // use the entity api tables EnableFeatureToggles: []string{ - featuremgmt.FlagUnifiedStorage, featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ @@ -194,7 +191,6 @@ func TestIntegrationPlaylist(t *testing.T) { DisableAnonymous: true, APIServerStorageType: "unified", // use the entity api tables EnableFeatureToggles: []string{ - featuremgmt.FlagUnifiedStorage, featuremgmt.FlagKubernetesPlaylists, // Required so that legacy calls are also written }, DualWriterDesiredModes: map[string]grafanarest.DualWriterMode{ From aa913b5f392ecd7ae7f8138fa08a4498af5c95a1 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Wed, 21 Aug 2024 13:24:45 -0400 Subject: [PATCH 207/229] chore: add tracing to quote API and service methods with contexts (#92211) * chore: add tracing to quote API and service methods with contexts I also fixed a typo (overriden -> overridden) and removed a method that looked like it wasn't useful anymore. (It seemed to exist to return an error, but never returned an error, and so just added many lines of unnecessary error checking). --- pkg/api/quota.go | 16 ++++++--- pkg/services/quota/quotaimpl/quota.go | 49 ++++++++++++++------------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/pkg/api/quota.go b/pkg/api/quota.go index 42be8ac2a54..58ff3b11ca6 100644 --- a/pkg/api/quota.go +++ b/pkg/api/quota.go @@ -47,7 +47,9 @@ func (hs *HTTPServer) GetOrgQuotas(c *contextmodel.ReqContext) response.Response } func (hs *HTTPServer) getOrgQuotasHelper(c *contextmodel.ReqContext, orgID int64) response.Response { - q, err := hs.QuotaService.GetQuotasByScope(c.Req.Context(), quota.OrgScope, orgID) + ctx, span := hs.tracer.Start(c.Req.Context(), "api.getOrgQuotasHelper") + defer span.End() + q, err := hs.QuotaService.GetQuotasByScope(ctx, quota.OrgScope, orgID) if err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "failed to get quota", err) } @@ -70,6 +72,8 @@ func (hs *HTTPServer) getOrgQuotasHelper(c *contextmodel.ReqContext, orgID int64 // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) UpdateOrgQuota(c *contextmodel.ReqContext) response.Response { + ctx, span := hs.tracer.Start(c.Req.Context(), "api.UpdateOrgQuota") + defer span.End() cmd := quota.UpdateQuotaCmd{} var err error if err := web.Bind(c.Req, &cmd); err != nil { @@ -81,7 +85,7 @@ func (hs *HTTPServer) UpdateOrgQuota(c *contextmodel.ReqContext) response.Respon } cmd.Target = web.Params(c.Req)[":target"] - if err := hs.QuotaService.Update(c.Req.Context(), &cmd); err != nil { + if err := hs.QuotaService.Update(ctx, &cmd); err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "Failed to update org quotas", err) } return response.Success("Organization quota updated") @@ -114,12 +118,14 @@ func (hs *HTTPServer) UpdateOrgQuota(c *contextmodel.ReqContext) response.Respon // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) GetUserQuotas(c *contextmodel.ReqContext) response.Response { + ctx, span := hs.tracer.Start(c.Req.Context(), "api.GetUserQuotas") + defer span.End() id, err := strconv.ParseInt(web.Params(c.Req)[":id"], 10, 64) if err != nil { return response.Err(quota.ErrBadRequest.Errorf("id is invalid: %w", err)) } - q, err := hs.QuotaService.GetQuotasByScope(c.Req.Context(), quota.UserScope, id) + q, err := hs.QuotaService.GetQuotasByScope(ctx, quota.UserScope, id) if err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "Failed to get org quotas", err) } @@ -143,6 +149,8 @@ func (hs *HTTPServer) GetUserQuotas(c *contextmodel.ReqContext) response.Respons // 404: notFoundError // 500: internalServerError func (hs *HTTPServer) UpdateUserQuota(c *contextmodel.ReqContext) response.Response { + ctx, span := hs.tracer.Start(c.Req.Context(), "api.UpdateUserQuota") + defer span.End() cmd := quota.UpdateQuotaCmd{} var err error if err := web.Bind(c.Req, &cmd); err != nil { @@ -154,7 +162,7 @@ func (hs *HTTPServer) UpdateUserQuota(c *contextmodel.ReqContext) response.Respo } cmd.Target = web.Params(c.Req)[":target"] - if err := hs.QuotaService.Update(c.Req.Context(), &cmd); err != nil { + if err := hs.QuotaService.Update(ctx, &cmd); err != nil { return response.ErrOrFallback(http.StatusInternalServerError, "Failed to update org quotas", err) } return response.Success("Organization quota updated") diff --git a/pkg/services/quota/quotaimpl/quota.go b/pkg/services/quota/quotaimpl/quota.go index 16a4f16a949..e8867ad38e8 100644 --- a/pkg/services/quota/quotaimpl/quota.go +++ b/pkg/services/quota/quotaimpl/quota.go @@ -4,6 +4,7 @@ import ( "context" "sync" + "go.opentelemetry.io/otel" "golang.org/x/sync/errgroup" "github.com/grafana/grafana/pkg/infra/db" @@ -13,6 +14,10 @@ import ( "github.com/grafana/grafana/pkg/setting" ) +// tracer is the global tracer for the quota service. Tracer pulls the globally +// initialized tracer from the opentelemetry package. +var tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/quota/quotaimpl/service") + type serviceDisabled struct { } @@ -81,16 +86,20 @@ func (s *service) QuotaReached(c *contextmodel.ReqContext, targetSrv quota.Targe if c == nil { return false, nil } + ctx, span := tracer.Start(c.Req.Context(), "quota-service.QuotaReached") + defer span.End() params := "a.ScopeParameters{} if c.IsSignedIn { params.OrgID = c.SignedInUser.GetOrgID() params.UserID = c.UserID } - return s.CheckQuotaReached(c.Req.Context(), targetSrv, params) + return s.CheckQuotaReached(ctx, targetSrv, params) } func (s *service) GetQuotasByScope(ctx context.Context, scope quota.Scope, id int64) ([]quota.QuotaDTO, error) { + ctx, span := tracer.Start(ctx, "quota-service.GetQuotasByScope") + defer span.End() if err := scope.Validate(); err != nil { return nil, err } @@ -104,10 +113,7 @@ func (s *service) GetQuotasByScope(ctx context.Context, scope quota.Scope, id in scopeParams.UserID = id } - c, err := s.getContext(ctx) - if err != nil { - return nil, err - } + c := quota.FromContext(ctx, s.targetToSrv) customLimits, err := s.store.Get(c, &scopeParams) if err != nil { return nil, err @@ -160,6 +166,8 @@ func (s *service) GetQuotasByScope(ctx context.Context, scope quota.Scope, id in } func (s *service) Update(ctx context.Context, cmd *quota.UpdateQuotaCmd) error { + ctx, span := tracer.Start(ctx, "quota-service.Update") + defer span.End() targetFound := false knownTargets, err := s.defaultLimits.Targets() if err != nil { @@ -175,16 +183,15 @@ func (s *service) Update(ctx context.Context, cmd *quota.UpdateQuotaCmd) error { return quota.ErrInvalidTarget.Errorf("unknown quota target: %s", cmd.Target) } - c, err := s.getContext(ctx) - if err != nil { - return err - } + c := quota.FromContext(ctx, s.targetToSrv) return s.store.Update(c, cmd) } // CheckQuotaReached check that quota is reached for a target. If ScopeParameters are not defined, only global scope is checked func (s *service) CheckQuotaReached(ctx context.Context, targetSrv quota.TargetSrv, scopeParams *quota.ScopeParameters) (bool, error) { - targetSrvLimits, err := s.getOverridenLimits(ctx, targetSrv, scopeParams) + ctx, span := tracer.Start(ctx, "quota-service.CheckQuotaReached") + defer span.End() + targetSrvLimits, err := s.getOverriddenLimits(ctx, targetSrv, scopeParams) if err != nil { return false, err } @@ -233,10 +240,9 @@ func (s *service) CheckQuotaReached(ctx context.Context, targetSrv quota.TargetS } func (s *service) DeleteQuotaForUser(ctx context.Context, userID int64) error { - c, err := s.getContext(ctx) - if err != nil { - return err - } + ctx, span := tracer.Start(ctx, "quota-service.DeleteQuotaForUser") + defer span.End() + c := quota.FromContext(ctx, s.targetToSrv) return s.store.DeleteByUser(c, userID) } @@ -296,13 +302,12 @@ func (s *service) getReporters() <-chan reporter { return ch } -func (s *service) getOverridenLimits(ctx context.Context, targetSrv quota.TargetSrv, scopeParams *quota.ScopeParameters) (map[quota.Tag]int64, error) { +func (s *service) getOverriddenLimits(ctx context.Context, targetSrv quota.TargetSrv, scopeParams *quota.ScopeParameters) (map[quota.Tag]int64, error) { + ctx, span := tracer.Start(ctx, "quota-service.getOverriddenLimits") + defer span.End() targetSrvLimits := make(map[quota.Tag]int64) - c, err := s.getContext(ctx) - if err != nil { - return nil, err - } + c := quota.FromContext(ctx, s.targetToSrv) customLimits, err := s.store.Get(c, scopeParams) if err != nil { return targetSrvLimits, err @@ -331,6 +336,8 @@ func (s *service) getOverridenLimits(ctx context.Context, targetSrv quota.Target } func (s *service) getUsage(ctx context.Context, scopeParams *quota.ScopeParameters) (*quota.Map, error) { + ctx, span := tracer.Start(ctx, "quota-service.getUsage") + defer span.End() usage := "a.Map{} g, ctx := errgroup.WithContext(ctx) @@ -352,7 +359,3 @@ func (s *service) getUsage(ctx context.Context, scopeParams *quota.ScopeParamete return usage, nil } - -func (s *service) getContext(ctx context.Context) (quota.Context, error) { - return quota.FromContext(ctx, s.targetToSrv), nil -} From 2d0350e786b128af2d135964e565d8a832e0c7ff Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Wed, 21 Aug 2024 13:26:34 -0400 Subject: [PATCH 208/229] add spans to search v2 (#92223) add tracing spans to search v2 service --- pkg/services/searchV2/http.go | 9 +++++---- pkg/services/searchV2/service.go | 10 ++++++++++ pkg/services/searchV2/usage.go | 2 +- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/pkg/services/searchV2/http.go b/pkg/services/searchV2/http.go index 0b58e134695..2d82b50c55a 100644 --- a/pkg/services/searchV2/http.go +++ b/pkg/services/searchV2/http.go @@ -6,10 +6,9 @@ import ( "io" "net/http" - "github.com/prometheus/client_golang/prometheus" - "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/prometheus/client_golang/prometheus" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/api/routing" @@ -34,7 +33,9 @@ func (s *searchHTTPService) RegisterHTTPRoutes(storageRoute routing.RouteRegiste } func (s *searchHTTPService) doQuery(c *contextmodel.ReqContext) response.Response { - searchReadinessCheckResp := s.search.IsReady(c.Req.Context(), c.SignedInUser.GetOrgID()) + ctx, span := tracer.Start(c.Req.Context(), "searchV2.doQuery") + defer span.End() + searchReadinessCheckResp := s.search.IsReady(ctx, c.SignedInUser.GetOrgID()) if !searchReadinessCheckResp.IsReady { dashboardSearchNotServedRequestsCounter.With(prometheus.Labels{ "reason": searchReadinessCheckResp.Reason, @@ -59,7 +60,7 @@ func (s *searchHTTPService) doQuery(c *contextmodel.ReqContext) response.Respons return response.Error(http.StatusBadRequest, "error parsing body", err) } - resp := s.search.doDashboardQuery(c.Req.Context(), c.SignedInUser, c.SignedInUser.GetOrgID(), *query) + resp := s.search.doDashboardQuery(ctx, c.SignedInUser, c.SignedInUser.GetOrgID(), *query) if resp.Error != nil { return response.Error(http.StatusInternalServerError, "error handling search request", resp.Error) diff --git a/pkg/services/searchV2/service.go b/pkg/services/searchV2/service.go index b0ca3096dfc..d886ec649a0 100644 --- a/pkg/services/searchV2/service.go +++ b/pkg/services/searchV2/service.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" + "go.opentelemetry.io/otel" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" @@ -58,6 +59,7 @@ var ( Namespace: namespace, Subsystem: subsystem, }) + tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/searchv2") ) type StandardSearchService struct { @@ -120,6 +122,8 @@ func (s *StandardSearchService) IsDisabled() bool { } func (s *StandardSearchService) Run(ctx context.Context) error { + ctx, span := tracer.Start(ctx, "searchv2.Run") + defer span.End() orgQuery := &org.SearchOrgsQuery{} result, err := s.orgService.Search(ctx, orgQuery) if err != nil { @@ -146,6 +150,8 @@ func (s *StandardSearchService) RegisterDashboardIndexExtender(ext DashboardInde } func (s *StandardSearchService) getUser(ctx context.Context, backendUser *backend.User, orgId int64) (*user.SignedInUser, error) { + ctx, span := tracer.Start(ctx, "searchv2.getUser") + defer span.End() // TODO: get user & user's permissions from the request context var usr *user.SignedInUser @@ -204,6 +210,8 @@ func (s *StandardSearchService) getUser(ctx context.Context, backendUser *backen } func (s *StandardSearchService) DoDashboardQuery(ctx context.Context, user *backend.User, orgID int64, q DashboardQuery) *backend.DataResponse { + ctx, span := tracer.Start(ctx, "searchv2.DoDashboardQuery") + defer span.End() start := time.Now() signedInUser, err := s.getUser(ctx, user, orgID) @@ -232,6 +240,8 @@ func (s *StandardSearchService) DoDashboardQuery(ctx context.Context, user *back } func (s *StandardSearchService) doDashboardQuery(ctx context.Context, signedInUser *user.SignedInUser, orgID int64, q DashboardQuery) *backend.DataResponse { + ctx, span := tracer.Start(ctx, "searchv2.doDashboardQuery") + defer span.End() rsp := &backend.DataResponse{} filter, err := s.auth.GetDashboardReadFilter(ctx, orgID, signedInUser) diff --git a/pkg/services/searchV2/usage.go b/pkg/services/searchV2/usage.go index d3cc1de923d..3bbc4a7032b 100644 --- a/pkg/services/searchV2/usage.go +++ b/pkg/services/searchV2/usage.go @@ -45,7 +45,7 @@ var ( ) func updateUsageStats(ctx context.Context, reader *bluge.Reader, logger log.Logger, tracer tracing.Tracer) { - ctx, span := tracer.Start(ctx, "searchV2 updateUsageStats") + ctx, span := tracer.Start(ctx, "searchV2.updateUsageStats") defer span.End() req := bluge.NewAllMatches(bluge.NewTermQuery("panel").SetField(documentFieldKind)) for _, usage := range panelUsage { From 2f012860344d08029e7ad9ce86f7ec79ceee93ec Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Wed, 21 Aug 2024 14:32:01 -0400 Subject: [PATCH 209/229] Storage: Add go.mod for apistore (#92224) --- .golangci.toml | 28 + Dockerfile | 1 + go.mod | 2 +- go.sum | 2 + go.work | 1 + go.work.sum | 410 +++++++++++ pkg/aggregator/go.mod | 10 +- pkg/aggregator/go.sum | 20 +- pkg/apimachinery/go.mod | 4 +- pkg/apimachinery/go.sum | 8 +- pkg/apiserver/go.mod | 10 +- pkg/apiserver/go.sum | 20 +- pkg/build/go.mod | 30 +- pkg/build/go.sum | 64 +- pkg/promlib/go.mod | 8 +- pkg/promlib/go.sum | 16 +- pkg/storage/unified/apistore/go.mod | 106 +++ pkg/storage/unified/apistore/go.sum | 491 +++++++++++++ pkg/storage/unified/apistore/history.go | 15 +- pkg/storage/unified/resource/go.mod | 30 +- pkg/storage/unified/resource/go.sum | 923 +++--------------------- 21 files changed, 1254 insertions(+), 945 deletions(-) create mode 100644 pkg/storage/unified/apistore/go.mod create mode 100644 pkg/storage/unified/apistore/go.sum diff --git a/.golangci.toml b/.golangci.toml index f45ec1af4b7..e021433a9a6 100644 --- a/.golangci.toml +++ b/.golangci.toml @@ -125,6 +125,34 @@ files = [ "**/pkg/promlib/**/*" ] +[linters-settings.depguard.rules.storage-unified-resource] +list-mode = "lax" +allow = [ + "github.com/grafana/grafana/pkg/apimachinery", +] +deny = [ + { pkg = "github.com/grafana/grafana/pkg", desc = "pkg/storage/unified/resource is not allowed to import grafana core" } +] +files = [ + "./pkg/storage/unified/resource/*", + "./pkg/storage/unified/resource/**/*" +] + +[linters-settings.depguard.rules.storage-unified-apistore] +list-mode = "lax" +allow = [ + "github.com/grafana/grafana/pkg/apimachinery", + "github.com/grafana/grafana/pkg/apiserver", + "github.com/grafana/grafana/pkg/unified/resource", +] +deny = [ + { pkg = "github.com/grafana/grafana/pkg", desc = "pkg/storage/unified/apistore is not allowed to import grafana core" } +] +files = [ + "./pkg/storage/unified/apistore/*", + "./pkg/storage/unified/apistore/**/*" +] + [linters-settings.gocritic] enabled-checks = ["ruleguard"] [linters-settings.gocritic.settings.ruleguard] diff --git a/Dockerfile b/Dockerfile index 1240f4c53eb..ddc1398f7f2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -63,6 +63,7 @@ COPY pkg/build/go.* pkg/build/ COPY pkg/build/wire/go.* pkg/build/wire/ COPY pkg/promlib/go.* pkg/promlib/ COPY pkg/storage/unified/resource/go.* pkg/storage/unified/resource/ +COPY pkg/storage/unified/apistore/go.* pkg/storage/unified/apistore/ COPY pkg/semconv/go.* pkg/semconv/ COPY pkg/aggregator/go.* pkg/aggregator/ diff --git a/go.mod b/go.mod index 20e3ff95352..16cf9f44dfa 100644 --- a/go.mod +++ b/go.mod @@ -76,7 +76,7 @@ require ( github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20240812131556-611a23ff0f7f // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db // @grafana/identity-access-team - github.com/grafana/authlib/claims v0.0.0-20240814072707-6cffd53bb828 // @grafana/identity-access-team + github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db // @grafana/identity-access-team github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad github.com/grafana/cuetsy v0.1.11 // @grafana/grafana-as-code github.com/grafana/dataplane/examples v0.0.1 // @grafana/observability-metrics diff --git a/go.sum b/go.sum index 8631e0bc6bf..e8e11c8d242 100644 --- a/go.sum +++ b/go.sum @@ -2308,6 +2308,8 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20240812131556-611a23ff0f7f h1:c8QAFXkilBiF29xc7oKO2IkbGE3bp9NIKgiNLazdooY= github.com/grafana/alerting v0.0.0-20240812131556-611a23ff0f7f/go.mod h1:DLj8frbtCaITljC2jc0L85JQViPF3mPfOSiYhm1osso= +github.com/grafana/authlib v0.0.0-20240814072707-6cffd53bb828 h1:+4kEyS1l0qXhg6sVH0W2ZNVYQ5GQiLnD8MlMUKvQ83o= +github.com/grafana/authlib v0.0.0-20240814072707-6cffd53bb828/go.mod h1:71+xJm0AE6eNGNExUvnABtyEztQ/Acb53/TAdOgwdmc= github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db h1:z++X4DdoX+aNlZNT1ZY4cykiFay4+f077pa0AG48SGg= github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db/go.mod h1:ptt910z9KFfpVSIbSbXvTRR7tS19mxD7EtmVbbJi/WE= github.com/grafana/authlib/claims v0.0.0-20240814072707-6cffd53bb828 h1:Hk6Oe0o1yIfdm2+2F3yHLjuaktukGVEOjju2txQXu8c= diff --git a/go.work b/go.work index 6d2d055536e..862037b80de 100644 --- a/go.work +++ b/go.work @@ -12,6 +12,7 @@ use ( ./pkg/build/wire // skip:golangci-lint ./pkg/promlib ./pkg/semconv + ./pkg/storage/unified/apistore ./pkg/storage/unified/resource ./pkg/util/xorm // skip:golangci-lint ) diff --git a/go.work.sum b/go.work.sum index 06919c5662f..b6dfbca4091 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2,133 +2,390 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-2023080216373 buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= +cloud.google.com/go v0.112.2/go.mod h1:iEqjp//KquGIJV/m+Pk3xecgKNhV+ry+vVTsy4TbDms= cloud.google.com/go/accessapproval v1.7.5 h1:uzmAMSgYcnlHa9X9YSQZ4Q1wlfl4NNkZyQgho1Z6p04= +cloud.google.com/go/accessapproval v1.7.11 h1:MgtE8CI+YJWPGGHnxQ9z1VQqV87h+vSGy2MeM/m0ggQ= +cloud.google.com/go/accessapproval v1.7.11/go.mod h1:KGK3+CLDWm4BvjN0wFtZqdFUGhxlTvTF6PhAwQJGL4M= cloud.google.com/go/accesscontextmanager v1.8.5 h1:2GLNaNu9KRJhJBFTIVRoPwk6xE5mUDgD47abBq4Zp/I= +cloud.google.com/go/accesscontextmanager v1.8.11 h1:IQ3KLJmNKPgstN0ZcRw0niU4KfsiOZmzvcGCF+NT618= +cloud.google.com/go/accesscontextmanager v1.8.11/go.mod h1:nwPysISS3KR5qXipAU6cW/UbDavDdTBBgPohbkhGSok= cloud.google.com/go/aiplatform v1.60.0 h1:0cSrii1ZeLr16MbBoocyy5KVnrSdiQ3KN/vtrTe7RqE= +cloud.google.com/go/aiplatform v1.68.0 h1:EPPqgHDJpBZKRvv+OsB3cr0jYz3EL2pZ+802rBPcG8U= +cloud.google.com/go/aiplatform v1.68.0/go.mod h1:105MFA3svHjC3Oazl7yjXAmIR89LKhRAeNdnDKJczME= cloud.google.com/go/analytics v0.23.0 h1:Q+y94XH84jM8SK8O7qiY/PJRexb6n7dRbQ6PiUa4YGM= +cloud.google.com/go/analytics v0.23.6 h1:BY8ZY7hQwKBi+lNp1IkiMTOK4xe4lxZCeYv3S9ARXtE= +cloud.google.com/go/analytics v0.23.6/go.mod h1:cFz5GwWHrWQi8OHKP9ep3Z4pvHgGcG9lPnFQ+8kXsNo= cloud.google.com/go/apigateway v1.6.5 h1:sPXnpk+6TneKIrjCjcpX5YGsAKy3PTdpIchoj8/74OE= +cloud.google.com/go/apigateway v1.6.11 h1:VtEvpnqqY2T5gZBzo+p7C87yGH3omHUkPIbRQkmGS9I= +cloud.google.com/go/apigateway v1.6.11/go.mod h1:4KsrYHn/kSWx8SNUgizvaz+lBZ4uZfU7mUDsGhmkWfM= cloud.google.com/go/apigeeconnect v1.6.5 h1:CrfIKv9Go3fh/QfQgisU3MeP90Ww7l/sVGmr3TpECo8= +cloud.google.com/go/apigeeconnect v1.6.11 h1:CftZgGXFRLJeD2/5ZIdWuAMxW/88UG9tHhRPI/NY75M= +cloud.google.com/go/apigeeconnect v1.6.11/go.mod h1:iMQLTeKxtKL+sb0D+pFlS/TO6za2IUOh/cwMEtn/4g0= cloud.google.com/go/apigeeregistry v0.8.3 h1:C+QU2K+DzDjk4g074ouwHQGkoff1h5OMQp6sblCVreQ= +cloud.google.com/go/apigeeregistry v0.8.9 h1:3vLwk0tS9L++6ZyV4RDH4UCydfVoqxJbpWvqG6MTtUw= +cloud.google.com/go/apigeeregistry v0.8.9/go.mod h1:4XivwtSdfSO16XZdMEQDBCMCWDp3jkCBRhVgamQfLSA= cloud.google.com/go/apikeys v0.6.0 h1:B9CdHFZTFjVti89tmyXXrO+7vSNo2jvZuHG8zD5trdQ= cloud.google.com/go/appengine v1.8.5 h1:l2SviT44zWQiOv8bPoMBzW0vOcMO22iO0s+nVtVhdts= +cloud.google.com/go/appengine v1.8.11 h1:ZLoWWwakgRzRnXX2bsgk2g1sdzti3wq+ebunTJsZNog= +cloud.google.com/go/appengine v1.8.11/go.mod h1:xET3coaDUj+OP4TgnZlgQ+rG2R9fG2nblya13czP56Q= cloud.google.com/go/area120 v0.8.5 h1:vTs08KPLN/iMzTbxpu5ciL06KcsrVPMjz4IwcQyZ4uY= +cloud.google.com/go/area120 v0.8.11 h1:UID1dl7lW2zs8OpYVtVZ5WsXU9kUcxC1nd3nnToHW70= +cloud.google.com/go/area120 v0.8.11/go.mod h1:VBxJejRAJqeuzXQBbh5iHBYUkIjZk5UzFZLCXmzap2o= cloud.google.com/go/artifactregistry v1.14.7 h1:W9sVlyb1VRcUf83w7aM3yMsnp4HS4PoyGqYQNG0O5lI= +cloud.google.com/go/artifactregistry v1.14.13 h1:NNK4vYVA5NGQmbmYidfJhnfmYU6SSSRUM2oopNouJNs= +cloud.google.com/go/artifactregistry v1.14.13/go.mod h1:zQ/T4xoAFPtcxshl+Q4TJBgsy7APYR/BLd2z3xEAqRA= cloud.google.com/go/asset v1.17.2 h1:xgFnBP3luSbUcC9RWJvb3Zkt+y/wW6PKwPHr3ssnIP8= +cloud.google.com/go/asset v1.19.5 h1:/R2XZS6lR8oj/Y3L+epD2yy7mf44Zp62H4xZ4vzaR/Y= +cloud.google.com/go/asset v1.19.5/go.mod h1:sqyLOYaLLfc4ACcn3YxqHno+J7lRt9NJTdO50zCUcY0= cloud.google.com/go/assuredworkloads v1.11.5 h1:gCrN3IyvqY3cP0wh2h43d99CgH3G+WYs9CeuFVKChR8= +cloud.google.com/go/assuredworkloads v1.11.11 h1:pwZp9o8aF5QmX4Z0YNlRe1ZOUzDw0UALmkem3aPobZc= +cloud.google.com/go/assuredworkloads v1.11.11/go.mod h1:vaYs6+MHqJvLKYgZBOsuuOhBgNNIguhRU0Kt7JTGcnI= +cloud.google.com/go/auth v0.3.0/go.mod h1:lBv6NKTWp8E3LPzmO1TbiiRKc4drLOfHsgmlH9ogv5w= +cloud.google.com/go/auth v0.5.1/go.mod h1:vbZT8GjzDf3AVqCcQmqeeM32U9HBFc32vVVAbwDsa6s= +cloud.google.com/go/auth v0.6.1/go.mod h1:eFHG7zDzbXHKmjJddFG/rBlcGp6t25SwRUiEQSlO4x4= +cloud.google.com/go/auth v0.7.2/go.mod h1:VEc4p5NNxycWQTMQEDQF0bd6aTMb6VgYDXEwiJJQAbs= +cloud.google.com/go/auth v0.7.3/go.mod h1:HJtWUx1P5eqjy/f6Iq5KeytNpbAcGolPhOgyop2LlzA= +cloud.google.com/go/auth v0.8.0/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc= +cloud.google.com/go/auth/oauth2adapt v0.2.2/go.mod h1:wcYjgpZI9+Yu7LyYBg4pqSiaRkfEK3GQcpb7C/uyF1Q= +cloud.google.com/go/auth/oauth2adapt v0.2.3/go.mod h1:tMQXOfZzFuNuUxOypHlQEXgdfX5cuhwU+ffUuXRJE8I= cloud.google.com/go/automl v1.13.5 h1:ijiJy9sYWh75WrqImXsfWc1e3HR3iO+ef9fvW03Ig/4= +cloud.google.com/go/automl v1.13.11 h1:FBCLjGS+Did/wtRHqyS055bRs/EJXx3meTvHPcdZgk8= +cloud.google.com/go/automl v1.13.11/go.mod h1:oMJdXRDOVC+Eq3PnGhhxSut5Hm9TSyVx1aLEOgerOw8= cloud.google.com/go/baremetalsolution v1.2.4 h1:LFydisRmS7hQk9P/YhekwuZGqb45TW4QavcrMToWo5A= +cloud.google.com/go/baremetalsolution v1.2.10 h1:VvBiXT9QJ4VpNVyfzHhLScY1aymZxpQgOa20yUvgphw= +cloud.google.com/go/baremetalsolution v1.2.10/go.mod h1:eO2c2NMRy5ytcNPhG78KPsWGNsX5W/tUsCOWmYihx6I= cloud.google.com/go/batch v1.8.0 h1:2HK4JerwVaIcCh/lJiHwh6+uswPthiMMWhiSWLELayk= +cloud.google.com/go/batch v1.9.2 h1:o1RAjc0ExGAAm41YB9LbJZyJDgZR4M6SKyITsd/Smr4= +cloud.google.com/go/batch v1.9.2/go.mod h1:smqwS4sleDJVAEzBt/TzFfXLktmWjFNugGDWl8coKX4= cloud.google.com/go/beyondcorp v1.0.4 h1:qs0J0O9Ol2h1yA0AU+r7l3hOCPzs2MjE1d6d/kaHIKo= +cloud.google.com/go/beyondcorp v1.0.10 h1:K4blSIQZn3YO4F4LmvWrH52pb8Y0L3NOrwkf22+x67M= +cloud.google.com/go/beyondcorp v1.0.10/go.mod h1:G09WxvxJASbxbrzaJUMVvNsB1ZiaKxpbtkjiFtpDtbo= cloud.google.com/go/bigquery v1.59.1 h1:CpT+/njKuKT3CEmswm6IbhNu9u35zt5dO4yPDLW+nG4= +cloud.google.com/go/bigquery v1.62.0 h1:SYEA2f7fKqbSRRBHb7g0iHTtZvtPSPYdXfmqsjpsBwo= +cloud.google.com/go/bigquery v1.62.0/go.mod h1:5ee+ZkF1x/ntgCsFQJAQTM3QkAZOecfCmvxhkJsWRSA= +cloud.google.com/go/bigtable v1.27.2-0.20240802230159-f371928b558f h1:UR2/6M/bSN8PPQlhaq+57w21VZLcEvq4ujsHd1p/G2s= +cloud.google.com/go/bigtable v1.27.2-0.20240802230159-f371928b558f/go.mod h1:avmXcmxVbLJAo9moICRYMgDyTTPoV0MA0lHKnyqV4fQ= cloud.google.com/go/billing v1.18.2 h1:oWUEQvuC4JvtnqLZ35zgzdbuHt4Itbftvzbe6aEyFdE= +cloud.google.com/go/billing v1.18.9 h1:sGRWx7PvsfHuZyx151Xr6CrORIgjvCMO4GRabihSdQQ= +cloud.google.com/go/billing v1.18.9/go.mod h1:bKTnh8MBfCMUT1fzZ936CPN9rZG7ZEiHB2J3SjIjByc= cloud.google.com/go/binaryauthorization v1.8.1 h1:1jcyh2uIUwSZkJ/JmL8kd5SUkL/Krbv8zmYLEbAz6kY= +cloud.google.com/go/binaryauthorization v1.8.7 h1:ItT9uR/0/ok2Ru3LCcbSIBUPsKqTA49ZmxCupqQaeFo= +cloud.google.com/go/binaryauthorization v1.8.7/go.mod h1:cRj4teQhOme5SbWQa96vTDATQdMftdT5324BznxANtg= cloud.google.com/go/certificatemanager v1.7.5 h1:UMBr/twXvH3jcT5J5/YjRxf2tvwTYIfrpemTebe0txc= +cloud.google.com/go/certificatemanager v1.8.5 h1:ASC9N81NU8JnGzi9kiY2QTqtTgOziwGv48sjt3YG420= +cloud.google.com/go/certificatemanager v1.8.5/go.mod h1:r2xINtJ/4xSz85VsqvjY53qdlrdCjyniib9Jp98ZKKM= cloud.google.com/go/channel v1.17.5 h1:/omiBnyFjm4S1ETHoOmJbL7LH7Ljcei4rYG6Sj3hc80= +cloud.google.com/go/channel v1.17.11 h1:AkKyMl2pSoJxBQtjAd6LYOtMgOaCl/kuiKoSg/Gf/H4= +cloud.google.com/go/channel v1.17.11/go.mod h1:gjWCDBcTGQce/BSMoe2lAqhlq0dIRiZuktvBKXUawp0= cloud.google.com/go/cloudbuild v1.15.1 h1:ZB6oOmJo+MTov9n629fiCrO9YZPOg25FZvQ7gIHu5ng= +cloud.google.com/go/cloudbuild v1.16.5 h1:RvK5r8JBCLNg9XmfGPy05t3bmhLJV3Xh3sDHGHAATgM= +cloud.google.com/go/cloudbuild v1.16.5/go.mod h1:HXLpZ8QeYZgmDIWpbl9Gs22p6o6uScgQ/cV9HF9cIZU= cloud.google.com/go/clouddms v1.7.4 h1:Sr0Zo5EAcPQiCBgHWICg3VGkcdS/LLP1d9SR7qQBM/s= +cloud.google.com/go/clouddms v1.7.10 h1:EA3y9v5TZiAlwgHJh2vPOEelqYiCxXBYZRCNnGK5q+g= +cloud.google.com/go/clouddms v1.7.10/go.mod h1:PzHELq0QDyA7VaD9z6mzh2mxeBz4kM6oDe8YxMxd4RA= cloud.google.com/go/cloudtasks v1.12.6 h1:EUt1hIZ9bLv8Iz9yWaCrqgMnIU+Tdh0yXM1MMVGhjfE= +cloud.google.com/go/cloudtasks v1.12.12 h1:p91Brp4nJkyRRI/maYdO+FT+e9tU+2xoGr20s2rvalU= +cloud.google.com/go/cloudtasks v1.12.12/go.mod h1:8UmM+duMrQpzzRREo0i3x3TrFjsgI/3FQw3664/JblA= cloud.google.com/go/compute v1.25.1 h1:ZRpHJedLtTpKgr3RV1Fx23NuaAEN1Zfx9hw1u4aJdjU= cloud.google.com/go/compute v1.25.1/go.mod h1:oopOIR53ly6viBYxaDhBfJwzUAxf1zE//uf3IB011ls= +cloud.google.com/go/compute v1.27.4 h1:XM8ulx6crjdl09XBfji7viFgZOEQuIxBwKmjRH9Rtmc= +cloud.google.com/go/compute v1.27.4/go.mod h1:7JZS+h21ERAGHOy5qb7+EPyXlQwzshzrx1x6L9JhTqU= cloud.google.com/go/contactcenterinsights v1.13.0 h1:6Vs/YnDG5STGjlWMEjN/xtmft7MrOTOnOZYUZtGTx0w= +cloud.google.com/go/contactcenterinsights v1.13.6 h1:LRcI5RAlLIbjwT312sGt+gyXcaXTr+v7uEQlNyArO9g= +cloud.google.com/go/contactcenterinsights v1.13.6/go.mod h1:mL+DbN3pMQGaAbDC4wZhryLciwSwHf5Tfk4Itr72Zyk= cloud.google.com/go/container v1.31.0 h1:MAaNH7VRNPWEhvqOypq2j+7ONJKrKzon4v9nS3nLZe0= +cloud.google.com/go/container v1.38.0 h1:GP5zLamfvPZeOTifnGBSER/br76D5eJ97xhcXXrh5tM= +cloud.google.com/go/container v1.38.0/go.mod h1:U0uPBvkVWOJGY/0qTVuPS7NeafFEUsHSPqT5pB8+fCY= cloud.google.com/go/containeranalysis v0.11.4 h1:doJ0M1ljS4hS0D2UbHywlHGwB7sQLNrt9vFk9Zyi7vY= +cloud.google.com/go/containeranalysis v0.12.1 h1:Xb8Eu7vVmWR5nAl5WPTGTx/dCr+R+oF7VbuYV47EHHs= +cloud.google.com/go/containeranalysis v0.12.1/go.mod h1:+/lcJIQSFt45TC0N9Nq7/dPbl0isk6hnC4EvBBqyXsM= cloud.google.com/go/datacatalog v1.19.3 h1:A0vKYCQdxQuV4Pi0LL9p39Vwvg4jH5yYveMv50gU5Tw= +cloud.google.com/go/datacatalog v1.21.0 h1:vl0pQT9TZ5rKi9e69FgtXNCR7I8MVRj4+CnbeXhz6UQ= +cloud.google.com/go/datacatalog v1.21.0/go.mod h1:DB0QWF9nelpsbB0eR/tA0xbHZZMvpoFD1XFy3Qv/McI= cloud.google.com/go/dataflow v0.9.5 h1:RYHtcPhmE664+F0Je46p+NvFbG8z//KCXp+uEqB4jZU= +cloud.google.com/go/dataflow v0.9.11 h1:YIhStasKFDESaUdpnsHsp/5bACYL/yvW0OuZ6zPQ6nY= +cloud.google.com/go/dataflow v0.9.11/go.mod h1:CCLufd7I4pPfyp54qMgil/volrL2ZKYjXeYLfQmBGJs= cloud.google.com/go/dataform v0.9.2 h1:5e4eqGrd0iDTCg4Q+VlAao5j2naKAA7xRurNtwmUknU= +cloud.google.com/go/dataform v0.9.8 h1:oNtTx9PdH7aPnvrKIsPrh+Y6Mw+8Bw5/ZgLWVHAev/c= +cloud.google.com/go/dataform v0.9.8/go.mod h1:cGJdyVdunN7tkeXHPNosuMzmryx55mp6cInYBgxN3oA= cloud.google.com/go/datafusion v1.7.5 h1:HQ/BUOP8OIGJxuztpYvNvlb+/U+/Bfs9SO8tQbh61fk= +cloud.google.com/go/datafusion v1.7.11 h1:GVcVisjVKmoj1eNnIp3G3qjjo+7koHr0Kf8tF6Cjqe0= +cloud.google.com/go/datafusion v1.7.11/go.mod h1:aU9zoBHgYmoPp4dzccgm/Gi4xWDMXodSZlNZ4WNeptw= cloud.google.com/go/datalabeling v0.8.5 h1:GpIFRdm0qIZNsxqURFJwHt0ZBJZ0nF/mUVEigR7PH/8= +cloud.google.com/go/datalabeling v0.8.11 h1:7jSuJEAc7upeMmyICzqfU0OyxUV38JSWW+8r5GmoHX0= +cloud.google.com/go/datalabeling v0.8.11/go.mod h1:6IGUV3z7hlkAU5ndKVshv/8z+7pxE+k0qXsEjyzO1Xg= cloud.google.com/go/dataplex v1.14.2 h1:fxIfdU8fxzR3clhOoNI7XFppvAmndxDu1AMH+qX9WKQ= +cloud.google.com/go/dataplex v1.18.2 h1:bIU1r1YnsX6P1qTnaRnah/STHoLJ3EHUZVCjJl2+1Eo= +cloud.google.com/go/dataplex v1.18.2/go.mod h1:NuBpJJMGGQn2xctX+foHEDKRbizwuiHJamKvvSteY3Q= cloud.google.com/go/dataproc v1.12.0 h1:W47qHL3W4BPkAIbk4SWmIERwsWBaNnWm0P2sdx3YgGU= cloud.google.com/go/dataproc/v2 v2.4.0 h1:/u81Fd+BvCLp+xjctI1DiWVJn6cn9/s3Akc8xPH02yk= +cloud.google.com/go/dataproc/v2 v2.5.3 h1:OgTfUARkF8AfkNmoyT0wyLLXNh4LbT3l55s5gUlvFOk= +cloud.google.com/go/dataproc/v2 v2.5.3/go.mod h1:RgA5QR7v++3xfP7DlgY3DUmoDSTaaemPe0ayKrQfyeg= cloud.google.com/go/dataqna v0.8.5 h1:9ybXs3nr9BzxSGC04SsvtuXaHY0qmJSLIpIAbZo9GqQ= +cloud.google.com/go/dataqna v0.8.11 h1:bEUidOYRS0EQ7qHbZtcnospuks72iTapboszXU9poz8= +cloud.google.com/go/dataqna v0.8.11/go.mod h1:74Icl1oFKKZXPd+W7YDtqJLa+VwLV6wZ+UF+sHo2QZQ= cloud.google.com/go/datastore v1.15.0 h1:0P9WcsQeTWjuD1H14JIY7XQscIPQ4Laje8ti96IC5vg= +cloud.google.com/go/datastore v1.17.1 h1:6Me8ugrAOAxssGhSo8im0YSuy4YvYk4mbGvCadAH5aE= +cloud.google.com/go/datastore v1.17.1/go.mod h1:mtzZ2HcVtz90OVrEXXGDc2pO4NM1kiBQy8YV4qGe0ZM= cloud.google.com/go/datastream v1.10.4 h1:o1QDKMo/hk0FN7vhoUQURREuA0rgKmnYapB+1M+7Qz4= +cloud.google.com/go/datastream v1.10.10 h1:klGhjQCLoLIRHMzMFIqM73cPNKliGveqC+Vrms+ce6A= +cloud.google.com/go/datastream v1.10.10/go.mod h1:NqchuNjhPlISvWbk426/AU/S+Kgv7srlID9P5XOAbtg= cloud.google.com/go/deploy v1.17.1 h1:m27Ojwj03gvpJqCbodLYiVmE9x4/LrHGGMjzc0LBfM4= +cloud.google.com/go/deploy v1.21.0 h1:/qnNETfztKemA9JmUBOrnH/rG/XFkHOBHygN1Vy5lkg= +cloud.google.com/go/deploy v1.21.0/go.mod h1:PaOfS47VrvmYnxG5vhHg0KU60cKeWcqyLbMBjxS8DW8= cloud.google.com/go/dialogflow v1.49.0 h1:KqG0oxGE71qo0lRVyAoeBozefCvsMfcDzDjoLYSY0F4= +cloud.google.com/go/dialogflow v1.55.0 h1:H28O0WAm2waHpNAz2n9jbv8FApfXxeKAkfHObdP2MMk= +cloud.google.com/go/dialogflow v1.55.0/go.mod h1:0u0hSlJiFpMkMpMNoFrQETwDjaRm8Q8hYKv+jz5JeRA= cloud.google.com/go/dlp v1.11.2 h1:lTipOuJaSjlYnnotPMbEhKURLC6GzCMDDzVbJAEbmYM= +cloud.google.com/go/dlp v1.16.0 h1:mYjBqgVjseYXlx1TOOFsxSeZLboqxxKR7TqRGOG9vIU= +cloud.google.com/go/dlp v1.16.0/go.mod h1:LtPZxZAenBXKzvWIOB2hdHIXuEcK0wW0En8//u+/nNA= cloud.google.com/go/documentai v1.25.0 h1:lI62GMEEPO6vXJI9hj+G9WjOvnR0hEjvjokrnex4cxA= +cloud.google.com/go/documentai v1.31.0 h1:YRkFK+0ZgEciz1svDkuL9fjbQLq8xvVa1d3NUlhO6B4= +cloud.google.com/go/documentai v1.31.0/go.mod h1:5ajlDvaPyl9tc+K/jZE8WtYIqSXqAD33Z1YAYIjfad4= cloud.google.com/go/domains v0.9.5 h1:Mml/R6s3vQQvFPpi/9oX3O5dRirgjyJ8cksK8N19Y7g= +cloud.google.com/go/domains v0.9.11 h1:8peNiXtaMNIF9Wybci859M/yprFcEve1R2z08pErUBs= +cloud.google.com/go/domains v0.9.11/go.mod h1:efo5552kUyxsXEz30+RaoIS2lR7tp3M/rhiYtKXkhkk= cloud.google.com/go/edgecontainer v1.1.5 h1:tBY32km78ScpK2aOP84JoW/+wtpx5WluyPUSEE3270U= +cloud.google.com/go/edgecontainer v1.2.5 h1:wTo0ulZDSsDzeoVjICJZjZMzZ1Nn9y//AwAQlXbaTbs= +cloud.google.com/go/edgecontainer v1.2.5/go.mod h1:OAb6tElD3F3oBujFAup14PKOs9B/lYobTb6LARmoACY= cloud.google.com/go/errorreporting v0.3.0 h1:kj1XEWMu8P0qlLhm3FwcaFsUvXChV/OraZwA70trRR0= +cloud.google.com/go/errorreporting v0.3.1 h1:E/gLk+rL7u5JZB9oq72iL1bnhVlLrnfslrgcptjJEUE= +cloud.google.com/go/errorreporting v0.3.1/go.mod h1:6xVQXU1UuntfAf+bVkFk6nld41+CPyF2NSPCyXE3Ztk= cloud.google.com/go/essentialcontacts v1.6.6 h1:13eHn5qBnsawxI7mIrv4jRIEmQ1xg0Ztqw5ZGqtUNfA= +cloud.google.com/go/essentialcontacts v1.6.12 h1:JaQXS+qCFYs8yectfZHpzw4+NjTvFqTuDMCtfPzMvbw= +cloud.google.com/go/essentialcontacts v1.6.12/go.mod h1:UGhWTIYewH8Ma4wDRJp8cMAHUCeAOCKsuwd6GLmmQLc= cloud.google.com/go/eventarc v1.13.4 h1:ORkd6/UV5FIdA8KZQDLNZYKS7BBOrj0p01DXPmT4tE4= +cloud.google.com/go/eventarc v1.13.10 h1:HVJmOVc+7eVFAqMpJRrq0nY0KlYBEBVZW7Gz7TxTio8= +cloud.google.com/go/eventarc v1.13.10/go.mod h1:KlCcOMApmUaqOEZUpZRVH+p0nnnsY1HaJB26U4X5KXE= cloud.google.com/go/filestore v1.8.1 h1:X5G4y/vrUo1B8Nsz93qSWTMAcM8LXbGUldq33OdcdCw= +cloud.google.com/go/filestore v1.8.7 h1:LF9t5MClPyFJMuXdez/AjF1uyO9xHKUFF3GUqA+xFPI= +cloud.google.com/go/filestore v1.8.7/go.mod h1:dKfyH0YdPAKdYHqAR/bxZeil85Y5QmrEVQwIYuRjcXI= cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw= +cloud.google.com/go/firestore v1.16.0 h1:YwmDHcyrxVRErWcgxunzEaZxtNbc8QoFYA/JOEwDPgc= +cloud.google.com/go/firestore v1.16.0/go.mod h1:+22v/7p+WNBSQwdSwP57vz47aZiY+HrDkrOsJNhk7rg= cloud.google.com/go/functions v1.16.0 h1:IWVylmK5F6hJ3R5zaRW7jI5PrWhCvtBVU4axQLmXSo4= +cloud.google.com/go/functions v1.16.6 h1:tPe3/48RpjcFk96VeB6jOKQpK8nliGJLsgjh6pUOyFQ= +cloud.google.com/go/functions v1.16.6/go.mod h1:wOzZakhMueNQaBUJdf0yjsJIe0GBRu+ZTvdSTzqHLs0= cloud.google.com/go/gaming v1.10.1 h1:5qZmZEWzMf8GEFgm9NeC3bjFRpt7x4S6U7oLbxaf7N8= cloud.google.com/go/gkebackup v1.3.5 h1:iuE8KNtTsPOc79qeWoNS8zOWoXPD9SAdOmwgxtlCmh8= +cloud.google.com/go/gkebackup v1.5.4 h1:mufh0PNpvqbfLV+TcxzSGESX8jGBcjKgctldv7kwQns= +cloud.google.com/go/gkebackup v1.5.4/go.mod h1:V+llvHlRD0bCyrkYaAMJX+CHralceQcaOWjNQs8/Ymw= cloud.google.com/go/gkeconnect v0.8.5 h1:17d+ZSSXKqG/RwZCq3oFMIWLPI8Zw3b8+a9/BEVlwH0= +cloud.google.com/go/gkeconnect v0.8.11 h1:4bZAzvqhuv1uP+i4yG9cEMQ6ggdP26nBVjUgroPU6IM= +cloud.google.com/go/gkeconnect v0.8.11/go.mod h1:ejHv5ehbceIglu1GsMwlH0nZpTftjxEY6DX7tvaM8gA= cloud.google.com/go/gkehub v0.14.5 h1:RboLNFzf9wEMSo7DrKVBlf+YhK/A/jrLN454L5Tz99Q= +cloud.google.com/go/gkehub v0.14.11 h1:hQkVCcOiW/vPVYsthvKl1nje430/TpdFfgeIuqcYVOA= +cloud.google.com/go/gkehub v0.14.11/go.mod h1:CsmDJ4qbBnSPkoBltEubK6qGOjG0xNfeeT5jI5gCnRQ= cloud.google.com/go/gkemulticloud v1.1.1 h1:rsSZAGLhyjyE/bE2ToT5fqo1qSW7S+Ubsc9jFOcbhSI= +cloud.google.com/go/gkemulticloud v1.2.4 h1:6zV05tyl37HoEjCGGY+zHFNxnKQCjvVpiqWAUVgGaEs= +cloud.google.com/go/gkemulticloud v1.2.4/go.mod h1:PjTtoKLQpIRztrL+eKQw8030/S4c7rx/WvHydDJlpGE= cloud.google.com/go/grafeas v0.3.4 h1:D4x32R/cHX3MTofKwirz015uEdVk4uAxvZkZCZkOrF4= +cloud.google.com/go/grafeas v0.3.6 h1:7bcA10EBgTsxeAVypJhz2Dv3fhrdlO7Ml8l7ZZA2IkE= +cloud.google.com/go/grafeas v0.3.6/go.mod h1:to6ECAPgRO2xeqD8ISXHc70nObJuaKZThreQOjeOH3o= cloud.google.com/go/gsuiteaddons v1.6.5 h1:CZEbaBwmbYdhFw21Fwbo+C35HMe36fTE0FBSR4KSfWg= +cloud.google.com/go/gsuiteaddons v1.6.11 h1:zydWX0nVT0Ut/P1X25Sy+4Rqe2PH04IzhwlF1BJd8To= +cloud.google.com/go/gsuiteaddons v1.6.11/go.mod h1:U7mk5PLBzDpHhgHv5aJkuvLp9RQzZFpa8hgWAB+xVIk= +cloud.google.com/go/iam v1.1.8/go.mod h1:GvE6lyMmfxXauzNq8NbgJbeVQNspG+tcdL/W8QO1+zE= +cloud.google.com/go/iam v1.1.12/go.mod h1:9LDX8J7dN5YRyzVHxwQzrQs9opFFqn0Mxs9nAeB+Hhg= cloud.google.com/go/iap v1.9.4 h1:94zirc2r4t6KzhAMW0R6Dme005eTP6yf7g6vN4IhRrA= +cloud.google.com/go/iap v1.9.10 h1:j7jQqqSkZ2nWAOCiyaZfnJ+REycTJ2NP2dUEjLoW4aA= +cloud.google.com/go/iap v1.9.10/go.mod h1:pO0FEirrhMOT1H0WVwpD5dD9r3oBhvsunyBQtNXzzc0= cloud.google.com/go/ids v1.4.5 h1:xd4U7pgl3GHV+MABnv1BF4/Vy/zBF7CYC8XngkOLzag= +cloud.google.com/go/ids v1.4.11 h1:JhlR1d0XhMsj6YmSmbLbbXV5CGkffnUkPj0HNxJYNtc= +cloud.google.com/go/ids v1.4.11/go.mod h1:+ZKqWELpJm8WcRRsSvKZWUdkriu4A3XsLLzToTv3418= cloud.google.com/go/iot v1.7.5 h1:munTeBlbqI33iuTYgXy7S8lW2TCgi5l1hA4roSIY+EE= +cloud.google.com/go/iot v1.7.11 h1:UBqSUZA6+7bM+mv6uvhl8tVsyT2Fi50njtBFRbrKSlI= +cloud.google.com/go/iot v1.7.11/go.mod h1:0vZJOqFy9kVLbUXwTP95e0dWHakfR4u5IWqsKMGIfHk= +cloud.google.com/go/kms v1.18.4/go.mod h1:SG1bgQ3UWW6/KdPo9uuJnzELXY5YTTMJtDYvajiQ22g= +cloud.google.com/go/kms v1.18.5 h1:75LSlVs60hyHK3ubs2OHd4sE63OAMcM2BdSJc2bkuM4= +cloud.google.com/go/kms v1.18.5/go.mod h1:yXunGUGzabH8rjUPImp2ndHiGolHeWJJ0LODLedicIY= cloud.google.com/go/language v1.12.3 h1:iaJZg6K4j/2PvZZVcjeO/btcWWIllVRBhuTFjGO4LXs= +cloud.google.com/go/language v1.13.0 h1:6Pl97Ei85A3wBJwjXW2S/1IWeUvhQf/lIPQBItnp0FA= +cloud.google.com/go/language v1.13.0/go.mod h1:B9FbD17g1EkilctNGUDAdSrBHiFOlKNErLljO7jplDU= cloud.google.com/go/lifesciences v0.9.5 h1:gXvN70m2p+4zgJFzaz6gMKaxTuF9WJ0USYoMLWAOm8g= +cloud.google.com/go/lifesciences v0.9.11 h1:xyPSYICJWZElcELYgWCKs5PltyNX3TzOKaQAZA7d/I0= +cloud.google.com/go/lifesciences v0.9.11/go.mod h1:NMxu++FYdv55TxOBEvLIhiAvah8acQwXsz79i9l9/RY= cloud.google.com/go/logging v1.9.0 h1:iEIOXFO9EmSiTjDmfpbRjOxECO7R8C7b8IXUGOj7xZw= +cloud.google.com/go/logging v1.11.0 h1:v3ktVzXMV7CwHq1MBF65wcqLMA7i+z3YxbUsoK7mOKs= +cloud.google.com/go/logging v1.11.0/go.mod h1:5LDiJC/RxTt+fHc1LAt20R9TKiUTReDg6RuuFOZ67+A= cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg= +cloud.google.com/go/longrunning v0.5.6/go.mod h1:vUaDrWYOMKRuhiv6JBnn49YxCPz2Ayn9GqyjaBT8/mA= +cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng= +cloud.google.com/go/longrunning v0.5.9/go.mod h1:HD+0l9/OOW0za6UWdKJtXoFAX/BGg/3Wj8p10NeWF7c= +cloud.google.com/go/longrunning v0.5.11/go.mod h1:rDn7//lmlfWV1Dx6IB4RatCPenTwwmqXuiP0/RgoEO4= cloud.google.com/go/managedidentities v1.6.5 h1:+bpih1piZVLxla/XBqeSUzJBp8gv9plGHIMAI7DLpDM= +cloud.google.com/go/managedidentities v1.6.11 h1:YU6NtRRBX5R1f3a8ryqhh1dUb1/pt3rnhSO50b63yZY= +cloud.google.com/go/managedidentities v1.6.11/go.mod h1:df+8oZ1D4Eri+NrcpuiR5Hd6MGgiMqn0ZCzNmBYPS0A= cloud.google.com/go/maps v1.6.4 h1:EVCZAiDvog9So46460BGbCasPhi613exoaQbpilMVlk= +cloud.google.com/go/maps v1.11.6 h1:HMI0drvgnT+BtsjBofb1Z80P53n63ybmm7l+1w1og9I= +cloud.google.com/go/maps v1.11.6/go.mod h1:MOS/NN0L6b7Kumr8bLux9XTpd8+D54DYxBMUjq+XfXs= cloud.google.com/go/mediatranslation v0.8.5 h1:c76KdIXljQHSCb/Cy47S8H4s05A4zbK3pAFGzwcczZo= +cloud.google.com/go/mediatranslation v0.8.11 h1:QvO405ocKTmcJqjfqL1zps08yrKk8rE+0E1ZNSWfjbw= +cloud.google.com/go/mediatranslation v0.8.11/go.mod h1:3sNEm0fx61eHk7rfzBzrljVV9XKr931xI3OFacQBVFg= cloud.google.com/go/memcache v1.10.5 h1:yeDv5qxRedFosvpMSEswrqUsJM5OdWvssPHFliNFTc4= +cloud.google.com/go/memcache v1.10.11 h1:DGPEJOVL4Qix2GLKQKcgzGpNLD7gAnCFLr9ch9YSIhU= +cloud.google.com/go/memcache v1.10.11/go.mod h1:ubJ7Gfz/xQawQY5WO5pht4Q0dhzXBFeEszAeEJnwBHU= cloud.google.com/go/metastore v1.13.4 h1:dR7vqWXlK6IYR8Wbu9mdFfwlVjodIBhd1JRrpZftTEg= +cloud.google.com/go/metastore v1.13.10 h1:E5eAxzIRoVP0DrV+ZtTLMYkkjSs4fcfsbL7wv1mXV2U= +cloud.google.com/go/metastore v1.13.10/go.mod h1:RPhMnBxUmTLT1fN7fNbPqtH5EoGHueDxubmJ1R1yT84= cloud.google.com/go/monitoring v1.18.0 h1:NfkDLQDG2UR3WYZVQE8kwSbUIEyIqJUPl+aOQdFH1T4= +cloud.google.com/go/monitoring v1.20.3/go.mod h1:GPIVIdNznIdGqEjtRKQWTLcUeRnPjZW85szouimiczU= +cloud.google.com/go/monitoring v1.20.4 h1:zwcViK7mT9SV0kzKqLOI3spRadvsmvw/R9z1MHNeC0E= +cloud.google.com/go/monitoring v1.20.4/go.mod h1:v7F/UcLRw15EX7xq565N7Ae5tnYEE28+Cl717aTXG4c= cloud.google.com/go/networkconnectivity v1.14.4 h1:GBfXFhLyPspnaBE3nI/BRjdhW8vcbpT9QjE/4kDCDdc= +cloud.google.com/go/networkconnectivity v1.14.10 h1:2EE8pKiv1AI8fBdZCdiUjNgQ+TaBgwE4GxIze4fDdY0= +cloud.google.com/go/networkconnectivity v1.14.10/go.mod h1:f7ZbGl4CV08DDb7lw+NmMXQTKKjMhgCEEwFbEukWuOY= cloud.google.com/go/networkmanagement v1.9.4 h1:aLV5GcosBNmd6M8+a0ekB0XlLRexv4fvnJJrYnqeBcg= +cloud.google.com/go/networkmanagement v1.13.6 h1:6TGn7ZZXyj5rloN0vv5Aw0awYbfbheNRg8BKroT7/2g= +cloud.google.com/go/networkmanagement v1.13.6/go.mod h1:WXBijOnX90IFb6sberjnGrVtZbgDNcPDUYOlGXmG8+4= cloud.google.com/go/networksecurity v0.9.5 h1:+caSxBTj0E8OYVh/5wElFdjEMO1S/rZtE1152Cepchc= +cloud.google.com/go/networksecurity v0.9.11 h1:6wUzyHCwDEOkDbAJjT6jxsAi+vMfe3aj2JWwqSFVXOQ= +cloud.google.com/go/networksecurity v0.9.11/go.mod h1:4xbpOqCwplmFgymAjPFM6ZIplVC6+eQ4m7sIiEq9oJA= cloud.google.com/go/notebooks v1.11.3 h1:FH48boYmrWVQ6k0Mx/WrnNafXncT5iSYxA8CNyWTgy0= +cloud.google.com/go/notebooks v1.11.9 h1:c8I0EaLGqStRmvX29L7jb4mOrpigxn1mGyBt65OdS0s= +cloud.google.com/go/notebooks v1.11.9/go.mod h1:JmnRX0eLgHRJiyxw8HOgumW9iRajImZxr7r75U16uXw= cloud.google.com/go/optimization v1.6.3 h1:63NZaWyN+5rZEKHPX4ACpw3BjgyeuY8+rCehiCMaGPY= +cloud.google.com/go/optimization v1.6.9 h1:++U21U9LWFdgnnVFaq4kDeOafft6gI/CHzsiJ173c6U= +cloud.google.com/go/optimization v1.6.9/go.mod h1:mcvkDy0p4s5k7iSaiKrwwpN0IkteHhGmuW5rP9nXA5M= cloud.google.com/go/orchestration v1.8.5 h1:YHgWMlrPttIVGItgGfuvO2KM7x+y9ivN/Yk92pMm1a4= +cloud.google.com/go/orchestration v1.9.6 h1:xfczjtNDabsXTnDySAwD/TMfDSkcxEgH1rxfS6BVQtM= +cloud.google.com/go/orchestration v1.9.6/go.mod h1:gQvdIsHESZJigimnbUA8XLbYeFlSg/z+A7ppds5JULg= cloud.google.com/go/orgpolicy v1.12.1 h1:2JbXigqBJVp8Dx5dONUttFqewu4fP0p3pgOdIZAhpYU= +cloud.google.com/go/orgpolicy v1.12.7 h1:StymaN9vS7949m15Nwgf5aKd9yaRtzWJ4VqHdbXcOEM= +cloud.google.com/go/orgpolicy v1.12.7/go.mod h1:Os3GlUFRPf1UxOHTup5b70BARnhHeQNNVNZzJXPbWYI= cloud.google.com/go/osconfig v1.12.5 h1:Mo5jGAxOMKH/PmDY7fgY19yFcVbvwREb5D5zMPQjFfo= +cloud.google.com/go/osconfig v1.13.2 h1:IbbTg7jtTEn4+iEJwgbCYck5NLMOc2eKrqVpQb7Xx6c= +cloud.google.com/go/osconfig v1.13.2/go.mod h1:eupylkWQJCwSIEMkpVR4LqpgKkQi0mD4m1DzNCgpQso= cloud.google.com/go/oslogin v1.13.1 h1:1K4nOT5VEZNt7XkhaTXupBYos5HjzvJMfhvyD2wWdFs= +cloud.google.com/go/oslogin v1.13.7 h1:q9x7tjKtfBpXMpiJKwb5UyhMA3GrwmJHvx56uCEuS8M= +cloud.google.com/go/oslogin v1.13.7/go.mod h1:xq027cL0fojpcEcpEQdWayiDn8tIx3WEFYMM6+q7U+E= cloud.google.com/go/phishingprotection v0.8.5 h1:DH3WFLzEoJdW/6xgsmoDqOwT1xddFi7gKu0QGZQhpGU= +cloud.google.com/go/phishingprotection v0.8.11 h1:3Kr7TINZ+8pbdWe3JnJf9c84ibz60NRTvwLdVtI3SK8= +cloud.google.com/go/phishingprotection v0.8.11/go.mod h1:Mge0cylqVFs+D0EyxlsTOJ1Guf3qDgrztHzxZqkhRQM= cloud.google.com/go/policytroubleshooter v1.10.3 h1:c0WOzC6hz964QWNBkyKfna8A2jOIx1zzZa43Gx/P09o= +cloud.google.com/go/policytroubleshooter v1.10.9 h1:EHXkBYgHQtVH8P41G2xxmQbMwQh+o5ggno8l3/9CXaA= +cloud.google.com/go/policytroubleshooter v1.10.9/go.mod h1:X8HEPVBWz8E+qwI/QXnhBLahEHdcuPO3M9YvSj0LDek= cloud.google.com/go/privatecatalog v0.9.5 h1:UZ0assTnATXSggoxUIh61RjTQ4P9zCMk/kEMbn0nMYA= +cloud.google.com/go/privatecatalog v0.9.11 h1:t8dJpQf22H6COeDvp7TDl7+KuwLT6yVmqAVRIUIUj6U= +cloud.google.com/go/privatecatalog v0.9.11/go.mod h1:awEF2a8M6UgoqVJcF/MthkF8SSo6OoWQ7TtPNxUlljY= cloud.google.com/go/pubsub v1.36.1 h1:dfEPuGCHGbWUhaMCTHUFjfroILEkx55iUmKBZTP5f+Y= +cloud.google.com/go/pubsub v1.41.0 h1:ZPaM/CvTO6T+1tQOs/jJ4OEMpjtel0PTLV7j1JK+ZrI= +cloud.google.com/go/pubsub v1.41.0/go.mod h1:g+YzC6w/3N91tzG66e2BZtp7WrpBBMXVa3Y9zVoOGpk= cloud.google.com/go/pubsublite v1.8.1 h1:pX+idpWMIH30/K7c0epN6V703xpIcMXWRjKJsz0tYGY= +cloud.google.com/go/pubsublite v1.8.2 h1:jLQozsEVr+c6tOU13vDugtnaBSUy/PD5zK6mhm+uF1Y= +cloud.google.com/go/pubsublite v1.8.2/go.mod h1:4r8GSa9NznExjuLPEJlF1VjOPOpgf3IT6k8x/YgaOPI= cloud.google.com/go/recaptchaenterprise v1.3.1 h1:u6EznTGzIdsyOsvm+Xkw0aSuKFXQlyjGE9a4exk6iNQ= cloud.google.com/go/recaptchaenterprise/v2 v2.9.2 h1:U3Wfq12X9cVMuTpsWDSURnXF0Z9hSPTHj+xsnXDRLsw= +cloud.google.com/go/recaptchaenterprise/v2 v2.14.2 h1:80Mx0i3uyv5dPNUYsNPFk9GJ+19AmTlnWnXFCTC9NkI= +cloud.google.com/go/recaptchaenterprise/v2 v2.14.2/go.mod h1:MwPgdgvBkE46aWuuXeBTCB8hQJ88p+CpXInROZYCTkc= cloud.google.com/go/recommendationengine v0.8.5 h1:ineqLswaCSBY0csYv5/wuXJMBlxATK6Xc5jJkpiTEdM= +cloud.google.com/go/recommendationengine v0.8.11 h1:STJYdA/e/MAh2ZSdjss5YE/d0t0nt0WotBF9V0pgpPQ= +cloud.google.com/go/recommendationengine v0.8.11/go.mod h1:cEkU4tCXAF88a4boMFZym7U7uyxvVwcQtKzS85IbQio= cloud.google.com/go/recommender v1.12.1 h1:LVLYS3r3u0MSCxQSDUtLSkporEGi9OAE6hGvayrZNPs= +cloud.google.com/go/recommender v1.12.7 h1:asEAoj4a3inPCdH8nbPaZDJWhR/xwfKi4tuSmIlaS2I= +cloud.google.com/go/recommender v1.12.7/go.mod h1:lG8DVtczLltWuaCv4IVpNphONZTzaCC9KdxLYeZM5G4= cloud.google.com/go/redis v1.14.2 h1:QF0maEdVv0Fj/2roU8sX3NpiDBzP9ICYTO+5F32gQNo= +cloud.google.com/go/redis v1.16.4 h1:9CO6EcuM9/CpgtcjG6JZV+GFw3oDrRfwLwmvwo/uM1o= +cloud.google.com/go/redis v1.16.4/go.mod h1:unCVfLP5eFrVhGLDnb7IaSaWxuZ+7cBgwwBwbdG9m9w= cloud.google.com/go/resourcemanager v1.9.5 h1:AZWr1vWVDKGwfLsVhcN+vcwOz3xqqYxtmMa0aABCMms= +cloud.google.com/go/resourcemanager v1.9.11 h1:N8CmqszjKNOgJnrQVsg+g8VWIEGgcwsD5rPiay9cMC4= +cloud.google.com/go/resourcemanager v1.9.11/go.mod h1:SbNAbjVLoi2rt9G74bEYb3aw1iwvyWPOJMnij4SsmHA= cloud.google.com/go/resourcesettings v1.6.5 h1:BTr5MVykJwClASci/7Og4Qfx70aQ4n3epsNLj94ZYgw= +cloud.google.com/go/resourcesettings v1.7.4 h1:1VwLfvJi8QtGrKPwuisGqr6gcgaCSR6A57wIvN+fqkM= +cloud.google.com/go/resourcesettings v1.7.4/go.mod h1:seBdLuyeq+ol2u9G2+74GkSjQaxaBWF+vVb6mVzQFG0= cloud.google.com/go/retail v1.16.0 h1:Fn1GuAua1c6crCGqfJ1qMxG1Xh10Tg/x5EUODEHMqkw= +cloud.google.com/go/retail v1.17.4 h1:YJgpBwCarAPqzaJS8ycIhyn2sAQT1RhTJRiTVBjtJAI= +cloud.google.com/go/retail v1.17.4/go.mod h1:oPkL1FzW7D+v/hX5alYIx52ro2FY/WPAviwR1kZZTMs= cloud.google.com/go/run v1.3.4 h1:m9WDA7DzTpczhZggwYlZcBWgCRb+kgSIisWn1sbw2rQ= +cloud.google.com/go/run v1.4.0 h1:ai1rnbX92iPqWg9MrbDbebsxlUSAiOK6N9dEDDQeVA0= +cloud.google.com/go/run v1.4.0/go.mod h1:4G9iHLjdOC+CQ0CzA0+6nLeR6NezVPmlj+GULmb0zE4= cloud.google.com/go/scheduler v1.10.6 h1:5U8iXLoQ03qOB+ZXlAecU7fiE33+u3QiM9nh4cd0eTE= +cloud.google.com/go/scheduler v1.10.12 h1:8BxDXoHCcsAe2fXsvFrkBbTxgl+5JBrIy1+/HRS0nxY= +cloud.google.com/go/scheduler v1.10.12/go.mod h1:6DRtOddMWJ001HJ6MS148rtLSh/S2oqd2hQC3n5n9fQ= cloud.google.com/go/secretmanager v1.11.5 h1:82fpF5vBBvu9XW4qj0FU2C6qVMtj1RM/XHwKXUEAfYY= +cloud.google.com/go/secretmanager v1.13.5/go.mod h1:/OeZ88l5Z6nBVilV0SXgv6XJ243KP2aIhSWRMrbvDCQ= +cloud.google.com/go/secretmanager v1.13.6 h1:0ZEl/LuoB4xQsjVfQt3Gi/dZfOv36n4JmdPrMargzYs= +cloud.google.com/go/secretmanager v1.13.6/go.mod h1:x2ySyOrqv3WGFRFn2Xk10iHmNmvmcEVSSqc30eb1bhw= cloud.google.com/go/security v1.15.5 h1:wTKJQ10j8EYgvE8Y+KhovxDRVDk2iv/OsxZ6GrLP3kE= +cloud.google.com/go/security v1.17.4 h1:ERhxAa02mnMEIIAXvzje+qJ+yWniP6l5uOX+k9ELCaA= +cloud.google.com/go/security v1.17.4/go.mod h1:KMuDJH+sEB3KTODd/tLJ7kZK+u2PQt+Cfu0oAxzIhgo= cloud.google.com/go/securitycenter v1.24.4 h1:/5jjkZ+uGe8hZ7pvd7pO30VW/a+pT2MrrdgOqjyucKQ= +cloud.google.com/go/securitycenter v1.33.1 h1:K+jfFUTum2jl//uWCN+QKkKXRgidxTyGfGTqXPyDvUY= +cloud.google.com/go/securitycenter v1.33.1/go.mod h1:jeFisdYUWHr+ig72T4g0dnNCFhRwgwGoQV6GFuEwafw= cloud.google.com/go/servicecontrol v1.11.1 h1:d0uV7Qegtfaa7Z2ClDzr9HJmnbJW7jn0WhZ7wOX6hLE= cloud.google.com/go/servicedirectory v1.11.4 h1:da7HFI1229kyzIyuVEzHXip0cw0d+E0s8mjQby0WN+k= +cloud.google.com/go/servicedirectory v1.11.11 h1:8Ky2lY0CWJJIIlsc+rKTn6C3SqOuVEwT3brDC6TJCjk= +cloud.google.com/go/servicedirectory v1.11.11/go.mod h1:pnynaftaj9LmRLIc6t3r7r7rdCZZKKxui/HaF/RqYfs= cloud.google.com/go/servicemanagement v1.8.0 h1:fopAQI/IAzlxnVeiKn/8WiV6zKndjFkvi+gzu+NjywY= cloud.google.com/go/serviceusage v1.6.0 h1:rXyq+0+RSIm3HFypctp7WoXxIA563rn206CfMWdqXX4= cloud.google.com/go/shell v1.7.5 h1:3Fq2hzO0ZSyaqBboJrFkwwf/qMufDtqwwA6ep8EZxEI= +cloud.google.com/go/shell v1.7.11 h1:RobTXyL33DQITYQh//KJ9GjS4bsdj4fGmm2rkb/ywzM= +cloud.google.com/go/shell v1.7.11/go.mod h1:SywZHWac7onifaT9m9MmegYp3GgCLm+tgk+w2lXK8vg= cloud.google.com/go/spanner v1.57.0 h1:fJq+ZfQUDHE+cy1li0bJA8+sy2oiSGhuGqN5nqVaZdU= +cloud.google.com/go/spanner v1.65.0 h1:XK15cs9lFFQo5n4Wh9nfrcPXAxWln6NdodDiQKmoD08= +cloud.google.com/go/spanner v1.65.0/go.mod h1:dQGB+w5a67gtyE3qSKPPxzniedrnAmV6tewQeBY7Hxs= cloud.google.com/go/speech v1.21.1 h1:nuFc+Kj5B8de75nN4FdPyUbI2SiBoHZG6BLurXL56Q0= +cloud.google.com/go/speech v1.24.0 h1:3j+WpeBY57C0FDJxg317vpKgOLjL/kNxlcNPGSqXkqE= +cloud.google.com/go/speech v1.24.0/go.mod h1:HcVyIh5jRXM5zDMcbFCW+DF2uK/MSGN6Rastt6bj1ic= +cloud.google.com/go/storage v1.41.0/go.mod h1:J1WCa/Z2FcgdEDuPUY8DxT5I+d9mFKsCepp5vR6Sq80= cloud.google.com/go/storagetransfer v1.10.4 h1:dy4fL3wO0VABvzM05ycMUPFHxTPbJz9Em8ikAJVqSbI= +cloud.google.com/go/storagetransfer v1.10.10 h1:GfxaYqX+kwlrSrJAENNmRTCGmSTgvouvS3XhgwKpOT8= +cloud.google.com/go/storagetransfer v1.10.10/go.mod h1:8+nX+WgQ2ZJJnK8e+RbK/zCXk8T7HdwyQAJeY7cEcm0= cloud.google.com/go/talent v1.6.6 h1:JssV0CE3FNujuSWn7SkosOzg7qrMxVnt6txOfGcMSa4= +cloud.google.com/go/talent v1.6.12 h1:JN721EjG+UTfHVVaMhyxwKCCJPjUc8PiS0RnW/7kWfE= +cloud.google.com/go/talent v1.6.12/go.mod h1:nT9kNVuJhZX2QgqKZS6t6eCWZs5XEBYRBv6bIMnPmo4= cloud.google.com/go/texttospeech v1.7.5 h1:dxY2Q5mHCbrGa3oPR2O3PCicdnvKa1JmwGQK36EFLOw= +cloud.google.com/go/texttospeech v1.7.11 h1:jzko1ahItjLYEWr6n3lTIoBSinD1JzavEuDzYLWZNko= +cloud.google.com/go/texttospeech v1.7.11/go.mod h1:Ua125HU+WT2IkIo5MzQtuNpNEk72soShJQVdorZ1SAE= cloud.google.com/go/tpu v1.6.5 h1:C8YyYda8WtNdBoCgFwwBzZd+S6+EScHOxM/z1h0NNp8= +cloud.google.com/go/tpu v1.6.11 h1:uMrwnK05cocNt3OOp+mZ16xlvIKaXUt3QUXkUbG4LdM= +cloud.google.com/go/tpu v1.6.11/go.mod h1:W0C4xaSj1Ay3VX/H96FRvLt2HDs0CgdRPVI4e7PoCDk= cloud.google.com/go/trace v1.10.5 h1:0pr4lIKJ5XZFYD9GtxXEWr0KkVeigc3wlGpZco0X1oA= +cloud.google.com/go/trace v1.10.11/go.mod h1:fUr5L3wSXerNfT0f1bBg08W4axS2VbHGgYcfH4KuTXU= +cloud.google.com/go/trace v1.10.12 h1:GoGZv1iAXEa73HgSGNjRl2vKqp5/f2AeKqErRFXA2kg= +cloud.google.com/go/trace v1.10.12/go.mod h1:tYkAIta/gxgbBZ/PIzFxSH5blajgX4D00RpQqCG/GZs= cloud.google.com/go/translate v1.10.1 h1:upovZ0wRMdzZvXnu+RPam41B0mRJ+coRXFP2cYFJ7ew= +cloud.google.com/go/translate v1.10.3/go.mod h1:GW0vC1qvPtd3pgtypCv4k4U8B7EdgK9/QEF2aJEUovs= +cloud.google.com/go/translate v1.10.7 h1:W16MpZ2Z3TWoHbNHmyHz9As276lGVTSwxRcquv454R0= +cloud.google.com/go/translate v1.10.7/go.mod h1:mH/+8tvcItuy1cOWqU+/Y3iFHgkVUObNIQYI/kiFFiY= cloud.google.com/go/video v1.20.4 h1:TXwotxkShP1OqgKsbd+b8N5hrIHavSyLGvYnLGCZ7xc= +cloud.google.com/go/video v1.22.0 h1:+FTZi7NtT4FV2Y1j3zC3zYjaRrlGqKsZpbLweredEWM= +cloud.google.com/go/video v1.22.0/go.mod h1:CxPshUNAb1ucnzbtruEHlAal9XY+SPG2cFqC/woJzII= cloud.google.com/go/videointelligence v1.11.5 h1:mYaWH8uhUCXLJCN3gdXswKzRa2+lK0zN6/KsIubm6pE= +cloud.google.com/go/videointelligence v1.11.11 h1:zl8xijOEavernn/t6mZZ4fg0pIVc2yquHH73oj0Leo4= +cloud.google.com/go/videointelligence v1.11.11/go.mod h1:dab2Ca3AXT6vNJmt3/6ieuquYRckpsActDekLcsd6dU= cloud.google.com/go/vision v1.2.0 h1:/CsSTkbmO9HC8iQpxbK8ATms3OQaX3YQUeTMGCxlaK4= cloud.google.com/go/vision/v2 v2.8.0 h1:W52z1b6LdGI66MVhE70g/NFty9zCYYcjdKuycqmlhtg= +cloud.google.com/go/vision/v2 v2.8.6 h1:HyFEUXQa0SvlF0LASCn/x+juNCH4kIXQrUqi6SIcYvE= +cloud.google.com/go/vision/v2 v2.8.6/go.mod h1:G3v0uovxCye3u369JfrHGY43H6u/IQ08x9dw5aVH8yY= cloud.google.com/go/vmmigration v1.7.5 h1:5v9RT2vWyuw3pK2ox0HQpkoftO7Q7/8591dTxxQc79g= +cloud.google.com/go/vmmigration v1.7.11 h1:yqwkTPpvSw9dUfnl9/APAVrwO9UW1jJZtgbZpNQ+WdU= +cloud.google.com/go/vmmigration v1.7.11/go.mod h1:PmD1fDB0TEHGQR1tDZt9GEXFB9mnKKalLcTVRJKzcQA= cloud.google.com/go/vmwareengine v1.1.1 h1:EGdDi9QbqThfZq3ILcDK5g+m9jTevc34AY5tACx5v7k= +cloud.google.com/go/vmwareengine v1.2.0 h1:9Fjn/RoeOMo8UQt1TbXmmw7rJApC26BqnISAI1AERcc= +cloud.google.com/go/vmwareengine v1.2.0/go.mod h1:rPjCHu6hG9N8d6PhkoDWFkqL9xpbFY+ueVW+0pNFbZg= cloud.google.com/go/vpcaccess v1.7.5 h1:XyL6hTLtEM/eE4F1GEge8xUN9ZCkiVWn44K/YA7z1rQ= +cloud.google.com/go/vpcaccess v1.7.11 h1:1XgRP+Q2X6MvE/xnexpQ7ydgav+IO5UcKUIJEbL65J8= +cloud.google.com/go/vpcaccess v1.7.11/go.mod h1:a2cuAiSCI4TVK0Dt6/dRjf22qQvfY+podxst2VvAkcI= cloud.google.com/go/webrisk v1.9.5 h1:251MvGuC8wisNN7+jqu9DDDZAi38KiMXxOpA/EWy4dE= +cloud.google.com/go/webrisk v1.9.11 h1:2qwEqnXrToIv2Y4xvsUSxCk7R2Ki+3W2+GNyrytoKTQ= +cloud.google.com/go/webrisk v1.9.11/go.mod h1:mK6M8KEO0ZI7VkrjCq3Tjzw4vYq+3c4DzlMUDVaiswE= cloud.google.com/go/websecurityscanner v1.6.5 h1:YqWZrZYabG88TZt7364XWRJGhxmxhony2ZUyZEYMF2k= +cloud.google.com/go/websecurityscanner v1.6.11 h1:r3ePI3YN7ujwX8c9gIkgbVjYVwP4yQA4X2z6P7+HNxI= +cloud.google.com/go/websecurityscanner v1.6.11/go.mod h1:vhAZjksELSg58EZfUQ1BMExD+hxqpn0G0DuyCZQjiTg= cloud.google.com/go/workflows v1.12.4 h1:uHNmUiatTbPQ4H1pabwfzpfEYD4BBnqDHqMm2IesOh4= +cloud.google.com/go/workflows v1.12.10 h1:EGJeZmwgE71jxFOI5s9iKST2Bivif3DSzlqVbiXACXQ= +cloud.google.com/go/workflows v1.12.10/go.mod h1:RcKqCiOmKs8wFUEf3EwWZPH5eHc7Oq0kamIyOUCk0IE= +code.cloudfoundry.org/clock v1.1.0 h1:XLzC6W3Ah/Y7ht1rmZ6+QfPdt1iGWEAAtIZXgiaj57c= +code.cloudfoundry.org/clock v1.1.0/go.mod h1:yA3fxddT9RINQL2XHS7PS+OXxKCGhfrZmlNUCIM6AKo= contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9 h1:yxE46rQA0QaqPGqN2UnwXvgCrRqtjR1CsGSWVTRjvv4= +contrib.go.opencensus.io/exporter/aws v0.0.0-20230502192102-15967c811cec h1:CSNP8nIEQt4sZEo2sGUiWSmVJ9c5QdyIQvwzZAsn+8Y= +contrib.go.opencensus.io/exporter/aws v0.0.0-20230502192102-15967c811cec/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= contrib.go.opencensus.io/exporter/stackdriver v0.13.10 h1:a9+GZPUe+ONKUwULjlEOucMMG0qfSCCenlji0Nhqbys= +contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4= +contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc= contrib.go.opencensus.io/integrations/ocsql v0.1.7 h1:G3k7C0/W44zcqkpRSFyjU9f6HZkbwIrL//qqnlqWZ60= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= docker.io/go-docker v1.0.0 h1:VdXS/aNYQxyA9wdLD5z8Q8Ro688/hG8HzKxYVEVbE6s= @@ -138,18 +395,43 @@ git.sr.ht/~sbinet/gg v0.3.1 h1:LNhjNn8DerC8f9DHLz6lS0YYul/b602DUxDgGkd/Aik= github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d h1:j6oB/WPCigdOkxtuPl1VSIiLpy7Mdsu6phQffbF19Ng= github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc= github.com/Azure/azure-amqp-common-go/v3 v3.2.2 h1:CJpxNAGxP7UBhDusRUoaOn0uOorQyAYhQYLnNgkRhlY= +github.com/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84JqgmI5G73ibAmrk= +github.com/Azure/azure-amqp-common-go/v3 v3.2.3/go.mod h1:7rPmbSfszeovxGfc5fSAXE4ehlXQZHpMja2OtxC2Tas= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1 h1:o/Ws6bEqMeKZUfj1RRm3mQ51O8JGU5w+Qdg2AhHib6A= +github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1/go.mod h1:6QAMYBAbQeeKX+REFJMZ1nFWu9XLw/PPcjYpuc9RDFs= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0 h1:gggzg0SUMs6SQbEw+3LoSsYf9YMjkupeAnHMX8O9mmY= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.2.0/go.mod h1:+6KLcKIVgxoBDMqMO/Nvy7bZ9a0nbU3I1DtFQK3YvB4= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E= github.com/Azure/azure-service-bus-go v0.11.5 h1:EVMicXGNrSX+rHRCBgm/TRQ4VUZ1m3yAYM/AB2R/SOs= github.com/Azure/go-amqp v0.16.4 h1:/1oIXrq5zwXLHaoYDliJyiFjJSpJZMWGgtMX9e0/Z30= +github.com/Azure/go-amqp v0.17.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= +github.com/Azure/go-amqp v1.0.5 h1:po5+ljlcNSU8xtapHTe8gIc8yHxCzC03E8afH2g1ftU= +github.com/Azure/go-amqp v1.0.5/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12 h1:wkAZRgT/pn8HhFyzfe9UnqOjJYqlembgCTi72Bm/xKk= github.com/Azure/go-autorest/autorest/azure/auth v0.5.12/go.mod h1:84w/uV8E37feW2NCJ08uT9VBfjfUHpgLVnG2InYD6cg= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5 h1:0W/yGmFdTIT77fvdlGZ0LMISoLHFJ7Tx4U0yeB+uFs4= github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWVUNamOgh8YNrv4p27l3Wc55oVfpzg= +github.com/AzureAD/microsoft-authentication-library-for-go v0.5.1/go.mod h1:Vt9sXTKwMyGcOxSmLDMnGPgqsUg7m8pe215qMLrDXw4= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= @@ -161,6 +443,11 @@ github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oM github.com/CloudyKit/jet/v6 v6.2.0/go.mod h1:d3ypHeIRNo2+XyqnGA8s+aphtcVpjP5hPwP/Lzo7Ro4= github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0 h1:YNu23BtH0PKF+fg3ykSorCp6jSTjcEtfnYLzbmcjVRA= +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/IBM/sarama v1.40.1/go.mod h1:+5OFwA5Du9I6QrznhaMHsuwWdWZNMjaBSIxEWEgKOYE= github.com/IBM/sarama v1.43.0 h1:YFFDn8mMI2QL0wOrG0J2sFoVIAFl7hS9JQi2YZsXtJc= github.com/IBM/sarama v1.43.0/go.mod h1:zlE6HEbC/SMQ9mhEYaF7nNLYOUyrs0obySKCckWP9BM= github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= @@ -169,6 +456,7 @@ github.com/KimMachineGun/automemlimit v0.6.0 h1:p/BXkH+K40Hax+PuWWPQ478hPjsp9h1C github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.6 h1:U68crOE3y3MPttCMQGywZOLrTeF5HHJ3/vDBCJn9/bA= github.com/OneOfOne/xxhash v1.2.6/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -181,6 +469,8 @@ github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= github.com/Shopify/sarama v1.19.0 h1:9oksLxC6uxVPHPVYUmq6xhr1BOF/hHobWH2UzO67z1s= github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWsokNbMijUGhmcoBJc= +github.com/Shopify/toxiproxy/v2 v2.5.0 h1:i4LPT+qrSlKNtQf5QliVjdP08GyAH8+BUIc9gT0eahc= +github.com/Shopify/toxiproxy/v2 v2.5.0/go.mod h1:yhM2epWtAmel9CB8r2+L+PCmhH6yH2pITaPAo7jxJl0= github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5 h1:rFw4nCn9iMW+Vajsk51NtYIcwSTkXr+JGrMd36kTDJw= github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= @@ -219,15 +509,26 @@ github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhi github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e h1:QEF07wC0T1rKkctt1RINW/+RMTVmiwxETico2l3gxJA= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= +github.com/armon/go-metrics v0.4.0/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a h1:pv34s756C4pEXnjgPfGYgdhg/ZdajGhyOvzx8k+23nw= github.com/aws/aws-lambda-go v1.13.3 h1:SuCy7H3NLyp+1Mrfp+m80jcbi9KYWAs9/BXwppwRDzY= github.com/aws/aws-sdk-go v1.44.321/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1 h1:w/fPGB0t5rWwA43mux4e9ozFSH5zF1moQemlA131PWc= github.com/aws/aws-sdk-go-v2/service/kms v1.16.3 h1:nUP29LA4GZZPihNSo5ZcF4Rl73u+bN5IBRnrQA0jFK4= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk= +github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4 h1:EmIEXOjAdXtxa2OGM1VAajZV/i06Q8qd4kBpJd9/p1k= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 h1:NgRFYyFpiMD62y4VPXh4DosPFbZd4vdMVBWKk0VmWXc= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4/go.mod h1:TKKN7IQoM7uTnyuFm9bm9cw5P//ZYTl4m3htBWQ1G/c= github.com/aws/aws-sdk-go-v2/service/sns v1.17.4 h1:7TdmoJJBwLFyakXjfrGztejwY5Ie1JEto7YFfznCmAw= +github.com/aws/aws-sdk-go-v2/service/sns v1.31.3 h1:eSTEdxkfle2G98FE+Xl3db/XAXXVTJPNQo9K/Ar8oAI= +github.com/aws/aws-sdk-go-v2/service/sns v1.31.3/go.mod h1:1dn0delSO3J69THuty5iwP0US2Glt0mx2qBBlI13pvw= github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3 h1:uHjK81fESbGy2Y9lspub1+C6VN5W2UXTDo2A/Pm4G0U= +github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3 h1:Vjqy5BZCOIsn4Pj8xzyqgGmsSqzz7y/WXbN3RgOoVrc= +github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3/go.mod h1:L0enV3GCRd5iG9B64W35C4/hwsCB00Ib+DKVGTadKHI= github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1 h1:zc1YLcknvxdW/i1MuJKmEnFB2TNkOfguuQaGRvJXPng= +github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4 h1:hgSBvRT7JEWx2+vEGI9/Ld5rZtl7M5lu8PqdvOmbRHw= +github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4/go.mod h1:v7NIzEFIHBiicOMaMTuEmbnzGnqW0d+6ulNALul6fYE= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= @@ -314,8 +615,10 @@ github.com/drone/drone-yaml v1.2.3/go.mod h1:QsqliFK8nG04AHFN9tTn9XJomRBQHD4wcej github.com/drone/funcmap v0.0.0-20211123105308-29742f68a7d1 h1:E8hjIYiEyI+1S2XZSLpMkqT9V8+YMljFNBWrFpuVM3A= github.com/drone/funcmap v0.0.0-20211123105308-29742f68a7d1/go.mod h1:Hph0/pT6ZxbujnE1Z6/08p5I0XXuOsppqF6NQlGOK0E= github.com/drone/signal v1.0.0 h1:NrnM2M/4yAuU/tXs6RP1a1ZfxnaHwYkd0kJurA1p6uI= +github.com/eapache/go-resiliency v1.3.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= github.com/eapache/go-resiliency v1.6.0 h1:CqGDTLtpwuWKn6Nj3uNUdflaq+/kIPsg0gfNzHton30= github.com/eapache/go-resiliency v1.6.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= +github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= @@ -358,6 +661,7 @@ github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM= +github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -378,6 +682,7 @@ github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4 h1:vF83LI8tAakwEwvWZtr github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= +github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4= github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= @@ -387,12 +692,20 @@ github.com/google/go-jsonnet v0.18.0 h1:/6pTy6g+Jh1a1I2UMoAODkqELFiVIdOxbNwv0DDz github.com/google/go-jsonnet v0.18.0/go.mod h1:C3fTzyVJDslXdiTqw/bTFk7vSGyCtH3MGRbDfvEwGd0= github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9 h1:OF1IPgv+F4NmqmJ98KTjdN97Vs1JxDPB3vbmYzV2dpk= github.com/google/go-replayers/grpcreplay v1.1.0 h1:S5+I3zYyZ+GQz68OfbURDdt/+cSMqCK1wrvNx7WBzTE= +github.com/google/go-replayers/grpcreplay v1.3.0 h1:1Keyy0m1sIpqstQmgz307zhiJ1pV4uIlFds5weTmxbo= +github.com/google/go-replayers/grpcreplay v1.3.0/go.mod h1:v6NgKtkijC0d3e3RW8il6Sy5sqRVUwoQa4mHOGEy8DI= github.com/google/go-replayers/httpreplay v1.1.1 h1:H91sIMlt1NZzN7R+/ASswyouLJfW0WLW7fhyUFvDEkY= +github.com/google/go-replayers/httpreplay v1.2.0 h1:VM1wEyyjaoU53BwrOnaf9VhAyQQEEioJvFYxYcLRKzk= +github.com/google/go-replayers/httpreplay v1.2.0/go.mod h1:WahEFFZZ7a1P4VM1qEeHy+tME4bwyqPcwWbNlUI1Mcg= github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/googleapis/cloud-bigtable-clients-test v0.0.2 h1:S+sCHWAiAc+urcEnvg5JYJUOdlQEm/SEzQ/c/IdAH5M= +github.com/googleapis/cloud-bigtable-clients-test v0.0.2/go.mod h1:mk3CrkrouRgtnhID6UZQDK3DrFFa7cYCAJcEmNsHYrY= github.com/googleapis/gax-go/v2 v2.12.1/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= +github.com/googleapis/gax-go/v2 v2.12.4/go.mod h1:KYEYLorsnIGDi/rPC8b5TdlB9kbKoFubselGIoBMCwI= +github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720 h1:zC34cGQu69FG7qzJ3WiKW244WfhDC3xxYMeNOX2gtUQ= github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= @@ -402,9 +715,14 @@ github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1/go.mod h1:YA9We4kTafu7mlMnUh3In6Q2wpg8fYN3ycgCKOK1TB8= +github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU= github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= @@ -412,10 +730,15 @@ github.com/hamba/avro/v2 v2.17.2 h1:6PKpEWzJfNnvBgn7m2/8WYaDOUASxfDU+Jyb4ojDgFY= github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc= github.com/hanwen/go-fuse/v2 v2.1.0 h1:+32ffteETaLYClUj0a3aHjZ1hOPxxaNEHiZiujuDaek= +github.com/hanwen/go-fuse/v2 v2.5.1 h1:OQBE8zVemSocRxA4OaFJbjJ5hlpCmIWbGr7r0M4uoQQ= +github.com/hanwen/go-fuse/v2 v2.5.1/go.mod h1:xKwi1cF7nXAOBCXujD5ie0ZKsxc8GGSA1rlMJc+8IJs= +github.com/hashicorp/consul/api v1.14.0/go.mod h1:bcaw5CSZ7NE9qfOfKCI1xb7ZKjzu/MyvQkCLTfqLqxQ= github.com/hashicorp/consul/api v1.15.3/go.mod h1:/g/qgcoBcEXALCNZgRRisyTW0nY86++L0KbeAMXYCeY= +github.com/hashicorp/consul/sdk v0.10.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= github.com/hashicorp/consul/sdk v0.11.0/go.mod h1:yPkX5Q6CsxTFMjQQDJwzeNmUUF5NUGGbrDsv9wTb8cw= github.com/hashicorp/consul/sdk v0.16.0 h1:SE9m0W6DEfgIVCJX7xU+iv/hUl4m/nxqMTnCdMxDpJ8= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.2/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-syslog v1.0.0 h1:KaodqZuhUoZereWVIYmpUgZysurB1kBLX2j0MwMrUAE= github.com/hashicorp/go.net v0.0.1 h1:sNCoNyDEvN1xa+X0baata4RdcpKwcMS6DH+xwfqPgjw= @@ -423,6 +746,8 @@ github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyf github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/mdns v1.0.4 h1:sY0CMhFmjIPDMlTB+HfymFHCaYLhgifZ0QhjaYKD/UQ= github.com/hashicorp/memberlist v0.3.1/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.4.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.0/go.mod h1:bXN03oZc5xlH46k/K1qTrpXb9ERKyY1/i/N5mxvgrZw= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hudl/fargo v1.4.0 h1:ZDDILMbB37UlAVLlWcJ2Iz1XuahZZTDZfdCKeclfq2s= @@ -436,19 +761,28 @@ github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZb github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY= github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w= +github.com/jackc/pgx/v4 v4.18.3 h1:dE2/TrEsGX3RBprb3qryqSV9Y60iZN1C6i8IrmW9/BA= +github.com/jackc/pgx/v4 v4.18.3/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw= github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1 h1:9Xm8CKtMZIXgcopfdWk/qZ1rt0HjMgfMR9nxxSeK6vk= github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1/go.mod h1:zuHl3Hh+e9P6gmBPvcqR1HjkaWHC/csgyskg6IaFKFo= github.com/jaegertracing/jaeger v1.55.0 h1:IJHzKb2B9EYQyKlE7VSoKzNP3emHeqZWnWrKj+kYzzs= github.com/jaegertracing/jaeger v1.55.0/go.mod h1:S884Mz8H+iGI8Ealq6sM9QzSOeU6P+nbFkYw7uww8CI= github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= +github.com/jcmturner/gokrb5/v8 v8.4.3/go.mod h1:dqRwJGXznQrzw6cWmyo6kH+E7jksEQG/CyVWsJEsJO0= github.com/jedib0t/go-pretty/v6 v6.2.4 h1:wdaj2KHD2W+mz8JgJ/Q6L/T5dB7kyqEFI16eLq7GEmk= github.com/jedib0t/go-pretty/v6 v6.2.4/go.mod h1:+nE9fyyHGil+PuISTCrp7avEdo6bqoMwqZnuiK2r2a0= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= @@ -459,6 +793,8 @@ github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 h1:rp+c0RAYOWj8 github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901/go.mod h1:Z86h9688Y0wesXCyonoVr47MasHilkuLMqGhRZ4Hpak= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jon-whit/go-grpc-prometheus v1.4.0 h1:/wmpGDJcLXuEjXryWhVYEGt9YBRhtLwFEN7T+Flr8sw= github.com/jon-whit/go-grpc-prometheus v1.4.0/go.mod h1:iTPm+Iuhh3IIqR0iGZ91JJEg5ax6YQEe1I0f6vtBuao= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= @@ -487,7 +823,11 @@ github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3ro github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY= github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw2e+eeNT/SbGySq8ajECXJ9e4fPoLhY= +github.com/klauspost/compress v1.14.4/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s= github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4= github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs= @@ -523,6 +863,10 @@ github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvls github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= +github.com/microsoft/ApplicationInsights-Go v0.4.4 h1:G4+H9WNs6ygSCe6sUyxRc2U81TI5Es90b2t/MwX5KqY= +github.com/microsoft/ApplicationInsights-Go v0.4.4/go.mod h1:fKRUseBqkw6bDiXTs3ESTiU/4YTIHsQS4W3fP2ieF4U= +github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= +github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g= github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng= @@ -539,9 +883,15 @@ github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8 h1:P48LjvUQpT github.com/natessilva/dag v0.0.0-20180124060714-7194b8dcc5c4 h1:dnMxwus89s86tI8rcGVp2HwZzlz7c5o92VOy7dSckBQ= github.com/nats-io/jwt v1.2.2 h1:w3GMTO969dFg+UOKTmmyuu7IGdusK+7Ytlt//OYH/uU= github.com/nats-io/jwt/v2 v2.0.3 h1:i/O6cmIsjpcQyWDYNcq2JyZ3/VTF8SJ4JWluI5OhpvI= +github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a h1:lem6QCvxR0Y28gth9P+wV2K/zYUUAkJ+55U8cpS0p5I= +github.com/nats-io/jwt/v2 v2.2.1-0.20220330180145-442af02fd36a/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k= github.com/nats-io/nats-server/v2 v2.5.0 h1:wsnVaaXH9VRSg+A2MVg5Q727/CqxnmPLGFQ3YZYKTQg= +github.com/nats-io/nats-server/v2 v2.8.4 h1:0jQzze1T9mECg8YZEl8+WYUXb9JKluJfCBriPUtluB4= +github.com/nats-io/nats-server/v2 v2.8.4/go.mod h1:8zZa+Al3WsESfmgSs98Fi06dRWLH5Bnq90m5bKD/eT4= +github.com/nats-io/nats.go v1.15.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E= github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8= +github.com/nats-io/nkeys v0.4.5/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY= github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= @@ -611,6 +961,7 @@ github.com/performancecopilot/speed/v4 v4.0.0 h1:VxEDCmdkfbQYDlcr/GC9YoN9PQ6p8ul github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/phpdave11/gofpdf v1.4.2 h1:KPKiIbfwbvC/wOncwhrpRdXVj2CZTCFlw4wnoyjtHfQ= github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= +github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pires/go-proxyproto v0.7.0 h1:IukmRewDQFWC7kfnb66CSomk2q/seBuilHBYFwyq0Hs= github.com/pires/go-proxyproto v0.7.0/go.mod h1:Vz/1JPY/OACxWGQNIRY2BeyDmpoaWmEP40O9LbuiFR4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= @@ -621,10 +972,16 @@ github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXq github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= +github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= github.com/prometheus/common/assets v0.2.0 h1:0P5OrzoHrYBOSM1OigWL3mY8ZvV2N4zIE/5AahrSrfM= github.com/prometheus/exporter-toolkit v0.10.1-0.20230714054209-2f4150c63f97/go.mod h1:LoBCZeRh+5hX+fSULNyFnagYlQG/gBsyA/deNzROkq8= github.com/prometheus/statsd_exporter v0.26.0 h1:SQl3M6suC6NWQYEzOvIv+EF6dAMYEqIuZy+o4H9F5Ig= github.com/prometheus/statsd_exporter v0.26.0/go.mod h1:GXFLADOmBTVDrHc7b04nX8ooq3azG61pnECNqT7O5DM= +github.com/rabbitmq/amqp091-go v1.2.0/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM= +github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA= +github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc= +github.com/rakyll/embedmd v0.0.0-20171029212350-c8060a0752a2 h1:1jfy6i1g66ijpffgfaF/7pIFYZnSZzvo9P9DFkFmRIM= +github.com/rakyll/embedmd v0.0.0-20171029212350-c8060a0752a2/go.mod h1:7jOTMgqac46PZcF54q6l2hkLEG8op93fZu61KmxWDV4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/relvacode/iso8601 v1.4.0 h1:GsInVSEJfkYuirYFxa80nMLbH2aydgZpIf52gYZXUJs= @@ -725,6 +1082,10 @@ github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b h1:7gd+rd8P3bqcn/96gOZa3F5dpJr/vEiDQYlNb/y2uNs= go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= +go.einride.tech/aip v0.67.1 h1:d/4TW92OxXBngkSOwWS2CH5rez869KpKMaN44mdxkFI= +go.einride.tech/aip v0.67.1/go.mod h1:ZGX4/zKw8dcgzdLsrvpOOGxfxI2QSk12SlP7d6c0/XI= +go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c= +go.etcd.io/etcd/client/pkg/v3 v3.5.13/go.mod h1:XxHT4u1qU12E2+po+UVPrEeL94Um6zL58ppuJWXSAB8= go.opentelemetry.io/collector v0.97.0 h1:qyOju13byHIKEK/JehmTiGMj4pFLa4kDyrOCtTmjHU0= go.opentelemetry.io/collector v0.97.0/go.mod h1:V6xquYAaO2VHVu4DBK28JYuikRdZajh7DH5Vl/Y8NiA= go.opentelemetry.io/collector/component v0.97.0 h1:vanKhXl5nptN8igRH4PqVYHOILif653vaPIKv6LCZCI= @@ -793,6 +1154,7 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.4 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.48.0/go.mod h1:rdENBZMT2OE6Ne/KLwpiXudnAsbdrdBaqBvTN8M8BgA= go.opentelemetry.io/contrib/propagators/b3 v1.23.0 h1:aaIGWc5JdfRGpCafLRxMJbD65MfTa206AwSKkvGS0Hg= go.opentelemetry.io/contrib/propagators/b3 v1.23.0/go.mod h1:Gyz7V7XghvwTq+mIhLFlTgcc03UDroOg8vezs4NLhwU= +go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4= go.opentelemetry.io/otel/bridge/opencensus v1.26.0 h1:DZzxj9QjznMVoehskOJnFP2gsTCWtDTFBDvFhPAY7nc= go.opentelemetry.io/otel/bridge/opencensus v1.26.0/go.mod h1:rJiX0KrF5m8Tm1XE8jLczpAv5zUaDcvhKecFG0ZoFG4= go.opentelemetry.io/otel/bridge/opentracing v1.26.0 h1:Q/dHj0DOhfLMAs5u5ucAbC7gy66x9xxsZRLpHCJ4XhI= @@ -801,22 +1163,33 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1 h1:ZqR go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.23.1/go.mod h1:D7ynngPWlGJrqyGSDOdscuv7uqttfCE3jcBvffDv9y4= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.23.1 h1:q/Nj5/2TZRIt6PderQ9oU0M00fzoe8UZuINGw6ETGTw= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.23.1/go.mod h1:DTE9yAu6r08jU3xa68GiSeI7oRcSEQ2RpKbbQGO+dWM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.26.0/go.mod h1:z46paqbJ9l7c9fIPCXTqTGwhQZ5XoTIsfeFYWboizjs= go.opentelemetry.io/otel/exporters/prometheus v0.46.0 h1:I8WIFXR351FoLJYuloU4EgXbtNX2URfU/85pUPheIEQ= go.opentelemetry.io/otel/exporters/prometheus v0.46.0/go.mod h1:ztwVUHe5DTR/1v7PeuGRnU5Bbd4QKYwApWmuutKsJSs= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.23.1 h1:C8r95vDR125t815KD+b1tI0Fbc1pFnwHTBxkbIZ6Szc= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.23.1/go.mod h1:Qr0qomr64jentMtOjWMbtYeJMSuMSlsPEjmnRA2sWZ4= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0 h1:s0PHtIkN+3xrbDOpt2M8OTG92cWqUESvzh2MxiR5xY8= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.24.0/go.mod h1:hZlFbDbRt++MMPCCfSJfmhkGIWnX1h3XjkfxZUjLrIA= +go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= +go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= +go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs= go.opentelemetry.io/otel/sdk/metric v1.26.0 h1:cWSks5tfriHPdWFnl+qpX3P681aAYqlZHcAyHw5aU9Y= go.opentelemetry.io/otel/sdk/metric v1.26.0/go.mod h1:ClMFFknnThJCksebJwz7KIyEDHO+nTB6gK8obLy8RyE= +go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0= +go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= +go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc= golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= @@ -825,30 +1198,65 @@ golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG golang.org/x/net v0.0.0-20190921015927-1a5e07d1ff72/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220725212005-46097bf591d3/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4= google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= +google.golang.org/api v0.177.0/go.mod h1:srbhue4MLjkjbkux5p3dw/ocYOSZTaIEvf7bCOnFQDw= +google.golang.org/api v0.183.0/go.mod h1:q43adC5/pHoSZTx5h2mSmdF7NcyfW9JuDyIOJAgS9ZQ= +google.golang.org/api v0.187.0/go.mod h1:KIHlTc4x7N7gKKuVsdmfBXN13yEEWXWFURWY6SBp2gk= +google.golang.org/api v0.188.0/go.mod h1:VR0d+2SIiWOYG3r/jdm7adPW9hI2aRv9ETOSCQ9Beag= +google.golang.org/api v0.189.0/go.mod h1:FLWGJKb0hb+pU2j+rJqwbnsF+ym+fQs73rbJ+KAUgy8= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/genproto v0.0.0-20240528184218-531527333157/go.mod h1:ubQlAQnzejB8uZzszhrTCU2Fyp6Vi7ZE5nn0c3W8+qQ= +google.golang.org/genproto v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:s7iA721uChleev562UJO2OYB0PPT9CMFjV+Ce7VJH5M= +google.golang.org/genproto v0.0.0-20240722135656-d784300faade/go.mod h1:FfBgJBJg9GcpPvKIuHSZ/aE1g2ecGL74upMzGZjiGEY= +google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:mCr1K1c8kX+1iSBREvU3Juo11CB+QOEWxbRS01wWl5M= google.golang.org/genproto/googleapis/api v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= +google.golang.org/genproto/googleapis/api v0.0.0-20240429193739-8cf5692501f6/go.mod h1:10yRODfgim2/T8csjQsMPgZOMvtytXKTDRzH6HRGzRw= +google.golang.org/genproto/googleapis/api v0.0.0-20240513163218-0867130af1f8/go.mod h1:vPrPUTsDCYxXWjP7clS81mZ6/803D8K4iM9Ma27VKas= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= +google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= +google.golang.org/genproto/googleapis/api v0.0.0-20240617180043-68d350f18fd4/go.mod h1:px9SlOOZBg1wM1zdnr8jEL4CNGUBZ+ZKYtNPApNQc4c= +google.golang.org/genproto/googleapis/api v0.0.0-20240711142825-46eb208f015d/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= +google.golang.org/genproto/googleapis/api v0.0.0-20240722135656-d784300faade/go.mod h1:mw8MG/Qz5wfgYr6VqVCiZcHe/GJEfI+oGGDCohaVgB0= +google.golang.org/genproto/googleapis/api v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:AHT0dDg3SoMOgZGnZk29b5xTbPHMoEC8qthmBLJCpys= +google.golang.org/genproto/googleapis/api v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:OFMYQFHJ4TM3JRlWDZhJbZfra2uqc3WLBZiaaqP4DtU= google.golang.org/genproto/googleapis/bytestream v0.0.0-20240325203815-454cdb8f5daa h1:wBkzraZsSqhj1M4L/nMrljUU6XasJkgHvUsq8oRGwF0= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240730163845-b1a4ccb954bf h1:T4tsZBlZYXK3j40sQNP5MBO32I+rn6ypV1PpklsiV8k= +google.golang.org/genproto/googleapis/bytestream v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:5/MT647Cn/GGhwTpXC7QqcaR5Cnee4v4MKCU1/nwnIQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240221002015-b0ce06bbee7c/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240429193739-8cf5692501f6/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go.mod h1:I7Y+G38R2bu5j1aLzfFmQfTcU/WnFuqDwLZAbvKTKpM= google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= @@ -883,6 +1291,8 @@ modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= +nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0= +nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= rsc.io/binaryregexp v0.2.0 h1:HfqmD5MEmC0zvwBuF187nq9mdnXjXsSivRiXN7SmRkE= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/quote/v3 v3.1.0 h1:9JKUTTIUgS6kzR9mK1YuGKv6Nl+DijDNIc0ghT58FaY= diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index fdf3f050b1c..55398f00f5e 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -139,13 +139,13 @@ require ( golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect - golang.org/x/time v0.5.0 // indirect + golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.22.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect gonum.org/v1/gonum v0.14.0 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 // indirect google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index 0223817b123..365a5e0f8df 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -442,8 +442,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -461,8 +461,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -470,12 +470,12 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 h1:+/tmTy5zAieooKIXfzDm9KiA3Bv6JBwriRN9LY+yayk= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988/go.mod h1:4+X6GvPs+25wZKbQq9qyAXrwIRExv7w0Ea6MgZLZiDM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 h1:V71AcdLZr2p8dC9dbOIMCpqi4EmRl8wUwnJzXXLmbmc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index cab2c972230..418e3beab91 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -4,7 +4,7 @@ go 1.23.0 require ( github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db // @grafana/identity-access-team - github.com/grafana/authlib/claims v0.0.0-20240814072707-6cffd53bb828 // @grafana/identity-access-team + github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db // @grafana/identity-access-team github.com/stretchr/testify v1.9.0 k8s.io/apimachinery v0.31.0 k8s.io/apiserver v0.31.0 @@ -37,7 +37,7 @@ require ( golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/text v0.17.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 // indirect google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 362a1b2d885..66b4df6cb6c 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -30,8 +30,8 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db h1:z++X4DdoX+aNlZNT1ZY4cykiFay4+f077pa0AG48SGg= github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db/go.mod h1:ptt910z9KFfpVSIbSbXvTRR7tS19mxD7EtmVbbJi/WE= -github.com/grafana/authlib/claims v0.0.0-20240814072707-6cffd53bb828 h1:Hk6Oe0o1yIfdm2+2F3yHLjuaktukGVEOjju2txQXu8c= -github.com/grafana/authlib/claims v0.0.0-20240814072707-6cffd53bb828/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -131,8 +131,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 h1:V71AcdLZr2p8dC9dbOIMCpqi4EmRl8wUwnJzXXLmbmc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 668022448f5..5e7937c7e4c 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -4,7 +4,7 @@ go 1.23.0 require ( github.com/google/go-cmp v0.6.0 - github.com/grafana/authlib/claims v0.0.0-20240814072707-6cffd53bb828 + github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 github.com/prometheus/client_golang v1.20.0 github.com/stretchr/testify v1.9.0 @@ -65,11 +65,11 @@ require ( golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect golang.org/x/text v0.17.0 // indirect - golang.org/x/time v0.5.0 // indirect + golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.22.0 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 // indirect google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index b3fabb8117e..66724337e95 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -77,8 +77,8 @@ 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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/authlib/claims v0.0.0-20240814072707-6cffd53bb828 h1:Hk6Oe0o1yIfdm2+2F3yHLjuaktukGVEOjju2txQXu8c= -github.com/grafana/authlib/claims v0.0.0-20240814072707-6cffd53bb828/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 h1:ItDcDxUjVLPKja+hogpqgW/kj8LxUL2qscelXIsN1Bs= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1/go.mod h1:DkxMin+qOh1Fgkxfbt+CUfBqqsCQJMG9op8Os/irBPA= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= @@ -260,8 +260,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -283,12 +283,12 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 h1:+/tmTy5zAieooKIXfzDm9KiA3Bv6JBwriRN9LY+yayk= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988/go.mod h1:4+X6GvPs+25wZKbQq9qyAXrwIRExv7w0Ea6MgZLZiDM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 h1:V71AcdLZr2p8dC9dbOIMCpqi4EmRl8wUwnJzXXLmbmc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= diff --git a/pkg/build/go.mod b/pkg/build/go.mod index 0cf69c6189e..c547676f24c 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -16,19 +16,18 @@ replace cuelang.org/go => github.com/grafana/cue v0.0.0-20230926092038-971951014 replace github.com/prometheus/prometheus => github.com/prometheus/prometheus v0.52.0 require ( - cloud.google.com/go/storage v1.38.0 // @grafana/grafana-backend-group + cloud.google.com/go/storage v1.43.0 // @grafana/grafana-backend-group github.com/Masterminds/semver/v3 v3.2.0 // @grafana/grafana-release-guild - github.com/aws/aws-sdk-go v1.51.31 // @grafana/aws-datasources + github.com/aws/aws-sdk-go v1.55.5 // @grafana/aws-datasources github.com/blang/semver/v4 v4.0.0 // @grafana/grafana-release-guild github.com/docker/docker v26.0.2+incompatible // @grafana/grafana-release-guild github.com/drone/drone-cli v1.6.1 // @grafana/grafana-release-guild github.com/gogo/protobuf v1.3.2 // indirect; @grafana/alerting-backend - github.com/golang/protobuf v1.5.4 // indirect; @grafana/grafana-backend-group github.com/google/go-cmp v0.6.0 // @grafana/grafana-backend-group github.com/google/go-github v17.0.0+incompatible // @grafana/grafana-release-guild github.com/google/go-github/v45 v45.2.0 // @grafana/grafana-release-guild github.com/google/uuid v1.6.0 // indirect; @grafana/grafana-backend-group - github.com/googleapis/gax-go/v2 v2.12.3 // indirect; @grafana/grafana-backend-group + github.com/googleapis/gax-go/v2 v2.13.0 // indirect; @grafana/grafana-backend-group github.com/jmespath/go-jmespath v0.4.0 // indirect; @grafana/grafana-backend-group github.com/stretchr/testify v1.9.0 // @grafana/grafana-backend-group github.com/urfave/cli v1.22.15 // @grafana/grafana-backend-group @@ -43,20 +42,20 @@ require ( golang.org/x/oauth2 v0.22.0 // @grafana/identity-access-team golang.org/x/sync v0.8.0 // indirect; @grafana/alerting-backend golang.org/x/text v0.17.0 // indirect; @grafana/grafana-backend-group - golang.org/x/time v0.5.0 // indirect; @grafana/grafana-backend-group + golang.org/x/time v0.6.0 // indirect; @grafana/grafana-backend-group golang.org/x/tools v0.22.0 // indirect; @grafana/grafana-as-code - google.golang.org/api v0.176.0 // @grafana/grafana-backend-group + google.golang.org/api v0.191.0 // @grafana/grafana-backend-group google.golang.org/grpc v1.65.0 // indirect; @grafana/plugins-platform-backend google.golang.org/protobuf v1.34.2 // indirect; @grafana/plugins-platform-backend gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend ) require ( - cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/auth v0.2.2 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.1 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect - cloud.google.com/go/iam v1.1.6 // indirect + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/auth v0.8.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect + cloud.google.com/go/iam v1.1.13 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/bmatcuk/doublestar v1.1.1 // indirect github.com/buildkite/yaml v2.1.0+incompatible // indirect @@ -75,7 +74,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/go-querystring v1.1.0 // indirect - github.com/google/s2a-go v0.1.7 // indirect + github.com/google/s2a-go v0.1.8 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect @@ -88,15 +87,16 @@ require ( go.opentelemetry.io/otel/metric v1.28.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect golang.org/x/sys v0.24.0 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect; @grafana/grafana-backend-group - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect; @grafana/grafana-backend-group + google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) require dagger.io/dagger v0.11.8-rc.2 require ( + cloud.google.com/go/longrunning v0.5.12 // indirect github.com/99designs/gqlgen v0.17.44 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Khan/genqlient v0.7.0 // indirect diff --git a/pkg/build/go.sum b/pkg/build/go.sum index ad13850ff46..5eef149b39b 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -1,16 +1,18 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= -cloud.google.com/go/auth v0.2.2 h1:gmxNJs4YZYcw6YvKRtVBaF2fyUE6UrWPyzU8jHvYfmI= -cloud.google.com/go/auth v0.2.2/go.mod h1:2bDNJWtWziDT3Pu1URxHHbkHE/BbOCuyUiKIGcNvafo= -cloud.google.com/go/auth/oauth2adapt v0.2.1 h1:VSPmMmUlT8CkIZ2PzD9AlLN+R3+D1clXMWHHa6vG/Ag= -cloud.google.com/go/auth/oauth2adapt v0.2.1/go.mod h1:tOdK/k+D2e4GEwfBRA48dKNQiDsqIXxLh7VU319eV0g= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= -cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= -cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= -cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo= +cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4= +cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus= +cloud.google.com/go/longrunning v0.5.12 h1:5LqSIdERr71CqfUsFlJdBpOkBH8FBCFD7P1nTWy3TYE= +cloud.google.com/go/longrunning v0.5.12/go.mod h1:S5hMV8CDJ6r50t2ubVJSKQVv5u0rmik5//KgLO3k4lU= +cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= +cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= dagger.io/dagger v0.11.8-rc.2 h1:HCP3gXgAfJJBFitJm0jRdKWJsIKgSWNmVN9UV+CkOdk= dagger.io/dagger v0.11.8-rc.2/go.mod h1:kIzxLfN8N8FXUCN9u5EHLBJUJMJm0t6XynecUzp0A5w= github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d/go.mod h1:3cARGAK9CfW3HoxCy1a0G4TKrdiKke8ftOMEOHyySYs= @@ -35,8 +37,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= -github.com/aws/aws-sdk-go v1.51.31 h1:4TM+sNc+Dzs7wY1sJ0+J8i60c6rkgnKP1pvPx8ghsSY= -github.com/aws/aws-sdk-go v1.51.31/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ= @@ -131,17 +133,17 @@ github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FC github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= -github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= @@ -308,8 +310,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -324,21 +326,19 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.176.0 h1:dHj1/yv5Dm/eQTXiP9hNCRT3xzJHWXeNdRq29XbMxoE= -google.golang.org/api v0.176.0/go.mod h1:Rra+ltKu14pps/4xTycZfobMgLpbosoaaL7c+SEMrO8= +google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk= +google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 h1:+/tmTy5zAieooKIXfzDm9KiA3Bv6JBwriRN9LY+yayk= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988/go.mod h1:4+X6GvPs+25wZKbQq9qyAXrwIRExv7w0Ea6MgZLZiDM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 h1:V71AcdLZr2p8dC9dbOIMCpqi4EmRl8wUwnJzXXLmbmc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index b5bad3aff38..f3aea762a82 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -22,7 +22,7 @@ require ( github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect github.com/apache/arrow/go/v15 v15.0.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.51.31 // indirect + github.com/aws/aws-sdk-go v1.55.5 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect @@ -118,10 +118,10 @@ require ( golang.org/x/sys v0.24.0 // indirect golang.org/x/text v0.17.0 // indirect golang.org/x/tools v0.22.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect gonum.org/v1/gonum v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 // indirect google.golang.org/grpc v1.65.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 3524fc61050..8a3cfef31d1 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -7,8 +7,8 @@ github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcy github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= -github.com/aws/aws-sdk-go v1.51.31 h1:4TM+sNc+Dzs7wY1sJ0+J8i60c6rkgnKP1pvPx8ghsSY= -github.com/aws/aws-sdk-go v1.51.31/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -348,14 +348,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 h1:+/tmTy5zAieooKIXfzDm9KiA3Bv6JBwriRN9LY+yayk= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988/go.mod h1:4+X6GvPs+25wZKbQq9qyAXrwIRExv7w0Ea6MgZLZiDM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 h1:V71AcdLZr2p8dC9dbOIMCpqi4EmRl8wUwnJzXXLmbmc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= diff --git a/pkg/storage/unified/apistore/go.mod b/pkg/storage/unified/apistore/go.mod new file mode 100644 index 00000000000..cc553c54044 --- /dev/null +++ b/pkg/storage/unified/apistore/go.mod @@ -0,0 +1,106 @@ +module github.com/grafana/grafana/pkg/storage/unified/apistore + +go 1.23.0 + +require ( + github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db + github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da + github.com/grafana/grafana/pkg/apiserver v0.0.0-20240821155123-6891eb1d35da + github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240821161612-71f0dae39e9d + github.com/stretchr/testify v1.9.0 + gocloud.dev v0.39.0 + google.golang.org/grpc v1.65.0 + k8s.io/apimachinery v0.31.0 + k8s.io/apiserver v0.31.0 + k8s.io/client-go v0.31.0 + k8s.io/klog/v2 v2.130.1 +) + +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/bufbuild/protocompile v0.4.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.11.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fullstorydev/grpchan v1.1.1 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/jsonreference v0.20.4 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // 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/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect + github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect + github.com/jhump/protoreflect v1.15.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_golang v1.20.0 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.etcd.io/etcd/api/v3 v3.5.14 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect + go.etcd.io/etcd/client/v3 v3.5.14 // indirect + go.opencensus.io v0.24.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/sdk v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect + go.opentelemetry.io/proto/otlp v1.3.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/oauth2 v0.22.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/term v0.23.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/time v0.6.0 // indirect + golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect + google.golang.org/api v0.191.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 // indirect + google.golang.org/protobuf v1.34.2 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.31.0 // indirect + k8s.io/component-base v0.31.0 // indirect + k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect + k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect + sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/pkg/storage/unified/apistore/go.sum b/pkg/storage/unified/apistore/go.sum new file mode 100644 index 00000000000..f779d2b7281 --- /dev/null +++ b/pkg/storage/unified/apistore/go.sum @@ -0,0 +1,491 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo= +cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wvAw= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4= +cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus= +cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= +cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= +github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM= +github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= +github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 h1:zeN9UtUlA6FTx0vFSayxSX32HDw73Yb6Hh2izDSFxXY= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10/go.mod h1:3HKuexPDcwLWPaqpW2UR/9n8N/u/3CKcGAzSs8p8u8g= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 h1:hT8ZAZRIfqBqHbzKTII+CIiY8G2oC9OpLedkZ51DWl8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/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/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= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas= +github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= +github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= +github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= +github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +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.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/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.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/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= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM= +github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= +github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= +github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= +github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db h1:z++X4DdoX+aNlZNT1ZY4cykiFay4+f077pa0AG48SGg= +github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db/go.mod h1:ptt910z9KFfpVSIbSbXvTRR7tS19mxD7EtmVbbJi/WE= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da h1:2E3c/I3ayAy4Z1GwIPqXNZcpUccRapE1aBXA1ho4g7o= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da/go.mod h1:p09fvU5ujNL/Ig8HB7g4f+S0zyYbQq3x/f0jA4ujVOM= +github.com/grafana/grafana/pkg/apiserver v0.0.0-20240821155123-6891eb1d35da h1:xQMb8cRZYu7D0IO9q/lB7qFQpLGAoPUnCase1CGHrXY= +github.com/grafana/grafana/pkg/apiserver v0.0.0-20240821155123-6891eb1d35da/go.mod h1:8kZIdcgyLiHBwXbZFFzg9XxM9zD8Ie3wkDhxWuqa5Oo= +github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240821161612-71f0dae39e9d h1:cmJmy/KdlD+8EOWn9AogfRMr9tWoWPDnZ180sQxD/IA= +github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240821161612-71f0dae39e9d/go.mod h1:KL0LyEIlmuRi/zzuCopFZSmSJzBVu2hMGHIK74i4iE8= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 h1:uGoIog/wiQHI9GAxXO5TJbT0wWKH3O9HhOJW1F9c3fY= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= +github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= +github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= +github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= +github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.etcd.io/bbolt v1.3.9 h1:8x7aARPEXiXbHmtUwAIv7eV2fQFHrLLavdiJ3uzJXoI= +go.etcd.io/bbolt v1.3.9/go.mod h1:zaO32+Ti0PK1ivdPtgMESzuzL2VPoIG1PCQNvOdo/dE= +go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= +go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= +go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= +go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= +go.etcd.io/etcd/client/v2 v2.305.13 h1:RWfV1SX5jTU0lbCvpVQe3iPQeAHETWdOTb6pxhd77C8= +go.etcd.io/etcd/client/v2 v2.305.13/go.mod h1:iQnL7fepbiomdXMb3om1rHq96htNNGv2sJkEcZGDRRg= +go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= +go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= +go.etcd.io/etcd/pkg/v3 v3.5.13 h1:st9bDWNsKkBNpP4PR1MvM/9NqUPfvYZx/YXegsYEH8M= +go.etcd.io/etcd/pkg/v3 v3.5.13/go.mod h1:N+4PLrp7agI/Viy+dUYpX7iRtSPvKq+w8Y14d1vX+m0= +go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= +go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= +go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= +go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0 h1:R3X6ZXmNPRR8ul6i3WgFURCHzaXjHdm0karRG/+dj3s= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.28.0/go.mod h1:QWFXnDavXWwMx2EEcZsf3yxgEKAqsxQ+Syjp+seyInw= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE= +go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +gocloud.dev v0.39.0 h1:EYABYGhAalPUaMrbSKOr5lejxoxvXj99nE8XFtsDgds= +gocloud.dev v0.39.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk= +google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 h1:+/tmTy5zAieooKIXfzDm9KiA3Bv6JBwriRN9LY+yayk= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988/go.mod h1:4+X6GvPs+25wZKbQq9qyAXrwIRExv7w0Ea6MgZLZiDM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 h1:V71AcdLZr2p8dC9dbOIMCpqi4EmRl8wUwnJzXXLmbmc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +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.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.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +k8s.io/api v0.31.0 h1:b9LiSjR2ym/SzTOlfMHm1tr7/21aD7fSkqgD/CVJBCo= +k8s.io/api v0.31.0/go.mod h1:0YiFF+JfFxMM6+1hQei8FY8M7s1Mth+z/q7eF1aJkTE= +k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= +k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= +k8s.io/apiserver v0.31.0 h1:p+2dgJjy+bk+B1Csz+mc2wl5gHwvNkC9QJV+w55LVrY= +k8s.io/apiserver v0.31.0/go.mod h1:KI9ox5Yu902iBnnyMmy7ajonhKnkeZYJhTZ/YI+WEMk= +k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8= +k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU= +k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs= +k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= +k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= +k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= +sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= +sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/storage/unified/apistore/history.go b/pkg/storage/unified/apistore/history.go index 9edb5114ef5..0bc09c8bbe0 100644 --- a/pkg/storage/unified/apistore/history.go +++ b/pkg/storage/unified/apistore/history.go @@ -3,15 +3,17 @@ package apistore import ( "context" "encoding/json" + "fmt" "net/http" "strconv" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" - "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/storage/unified/resource" ) @@ -57,7 +59,7 @@ func (r *historyREST) NewConnectOptions() (runtime.Object, bool, string) { } func (r *historyREST) Connect(ctx context.Context, uid string, opts runtime.Object, responder rest.Responder) (http.Handler, error) { - info, err := request.NamespaceInfoFrom(ctx, true) + info, err := NamespaceInfoFrom(ctx, true) if err != nil { return nil, err } @@ -101,3 +103,12 @@ func (r *historyREST) Connect(ctx context.Context, uid string, opts runtime.Obje responder.Object(http.StatusOK, list) }), nil } + +// TODO: This is a temporary copy of the function from pkg/services/apiserver/endpoints/request/namespace.go +func NamespaceInfoFrom(ctx context.Context, requireOrgID bool) (claims.NamespaceInfo, error) { + info, err := claims.ParseNamespace(request.NamespaceValue(ctx)) + if err == nil && requireOrgID && info.OrgID < 1 { + return info, fmt.Errorf("expected valid orgId in namespace") + } + return info, err +} diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 08718f722b9..360723aab76 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -4,23 +4,20 @@ go 1.23.0 require ( github.com/fullstorydev/grpchan v1.1.1 - github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1 - github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 + github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db + github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808164224-787abccfbc9e github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 - github.com/prometheus/client_golang v1.19.1 + github.com/prometheus/client_golang v1.20.0 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel/trace v1.28.0 - gocloud.dev v0.25.0 + gocloud.dev v0.39.0 google.golang.org/grpc v1.65.0 google.golang.org/protobuf v1.34.2 k8s.io/apimachinery v0.31.0 ) require ( - cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/storage v1.38.0 // indirect - github.com/aws/aws-sdk-go v1.51.31 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bufbuild/protocompile v0.4.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -32,7 +29,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/googleapis/gax-go/v2 v2.12.3 // indirect + github.com/googleapis/gax-go/v2 v2.13.0 // indirect github.com/jhump/protoreflect v1.15.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -46,15 +43,14 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect - golang.org/x/crypto v0.24.0 // indirect - golang.org/x/net v0.26.0 // indirect - golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect - golang.org/x/text v0.16.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/api v0.176.0 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + golang.org/x/crypto v0.26.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sync v0.8.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 // indirect + golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect + google.golang.org/api v0.191.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 4c6c5cccbc8..02d4c05722a 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -1,273 +1,98 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= -cloud.google.com/go/auth v0.2.2 h1:gmxNJs4YZYcw6YvKRtVBaF2fyUE6UrWPyzU8jHvYfmI= -cloud.google.com/go/auth v0.2.2/go.mod h1:2bDNJWtWziDT3Pu1URxHHbkHE/BbOCuyUiKIGcNvafo= -cloud.google.com/go/auth/oauth2adapt v0.2.1 h1:VSPmMmUlT8CkIZ2PzD9AlLN+R3+D1clXMWHHa6vG/Ag= -cloud.google.com/go/auth/oauth2adapt v0.2.1/go.mod h1:tOdK/k+D2e4GEwfBRA48dKNQiDsqIXxLh7VU319eV0g= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= -cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= -cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= -cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= -cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI= -cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= -cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4= -cloud.google.com/go/monitoring v1.4.0/go.mod h1:y6xnxfwI3hTFWOdkOaD7nfJVlwuC3/mS/5kvtT131p4= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.19.0/go.mod h1:/O9kmSe9bb9KRnIAWkzmqhPjHo6LtzGOBYd/kr06XSs= -cloud.google.com/go/secretmanager v1.3.0/go.mod h1:+oLTkouyiYiabAQNugCeTS3PAArGiMJuBqvJnJsyH+U= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA= -cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= -cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= -cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A= -cloud.google.com/go/trace v1.2.0/go.mod h1:Wc8y/uYyOhPy12KEnXG9XGrvfMz5F5SrYecQlbW1rwM= -contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= -contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8= -contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI= -github.com/Azure/azure-amqp-common-go/v3 v3.2.2/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI= -github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= -github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= -github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU= -github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= -github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= -github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.17/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= -github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/autorest/validation v0.3.1/go.mod h1:yhLgjC0Wda5DYXl6JAsWyUe4KVNffhoDhG0zVzUMo3E= -github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= +cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo= +cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= +cloud.google.com/go/compute v1.23.4 h1:EBT9Nw4q3zyE7G45Wvv3MzolIrCJEuHys5muLY0wvAw= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= +cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4= +cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus= +cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= +cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0/go.mod h1:spvB9eLJH9dutlbPSRmHvSXXHOwGRyeXh1jVdquA2G8= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/aws/aws-sdk-go v1.51.31 h1:4TM+sNc+Dzs7wY1sJ0+J8i60c6rkgnKP1pvPx8ghsSY= -github.com/aws/aws-sdk-go v1.51.31/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/aws/aws-sdk-go-v2 v1.16.2 h1:fqlCk6Iy3bnCumtrLz9r3mJ/2gUT0pJ0wLFVIdWh+JA= -github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM= -github.com/aws/aws-sdk-go-v2/config v1.15.3 h1:5AlQD0jhVXlGzwo+VORKiUuogkG7pQcLJNzIzK7eodw= -github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg= -github.com/aws/aws-sdk-go-v2/credentials v1.11.2 h1:RQQ5fzclAKJyY5TvF+fkjJEwzK4hnxQCLOu5JXzDmQo= -github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 h1:LWPg5zjHV9oz/myQr4wMs0gi4CjnDN/ILmyZUFYXZsU= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3 h1:ir7iEq78s4txFGgwcLqD6q9IIPzTQNRJXulJd9h/zQo= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 h1:onz/VaaxZ7Z4V+WIN9Txly9XLTmoOh1oJ8XcAC3pako= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 h1:9stUQR/u2KXU6HkFJYlqnZEjBnbgrVbG6I5HN09xZh0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 h1:by9P+oy3P/CwggN4ClnW2D4oL91QV7pBzBICi1chZvQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 h1:T4pFel53bkHjL2mMo+4DKE6r6AuoZnM0fg7k1/ratr4= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3 h1:I0dcwWitE752hVSMrsLCxqNQ+UdEp3nACx2bYNMQq+k= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 h1:Gh1Gpyh01Yvn7ilO/b/hr01WgNpaszfbKMUgqM186xQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3 h1:BKjwCJPnANbkwQ8vzSbaZDKawwagDubrH/z/c0X+kbQ= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc= -github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3 h1:rMPtwA7zzkSQZhhz9U3/SoIDz/NZ7Q+iRn4EIO8rSyU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o= -github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw= -github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM= -github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI+N/H1I1ynHwXnO6FowXJc0= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 h1:frW4ikGcxfAEDfmQqWgMLp+F1n4nRo9sF39OcIb5BkQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 h1:cJGRyzCSVwZC7zZZ1xbx9m32UnrKydRYhOvcD1NYP9Q= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8= -github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= -github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= +github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM= +github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= +github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 h1:zeN9UtUlA6FTx0vFSayxSX32HDw73Yb6Hh2izDSFxXY= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10/go.mod h1:3HKuexPDcwLWPaqpW2UR/9n8N/u/3CKcGAzSs8p8u8g= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 h1:hT8ZAZRIfqBqHbzKTII+CIiY8G2oC9OpLedkZ51DWl8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= +github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= +github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= -github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= -github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= -github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= -github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas= github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= -github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= @@ -277,196 +102,75 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -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.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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 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-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk= -github.com/google/go-replayers/httpreplay v1.1.1/go.mod h1:gN9GeLIs7l6NUoVaSSnv2RiqK1NiwAmD0MrKeC9IIks= 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= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= -github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= +github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= -github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1 h1:EiaupmOnt6XF/LPxvagjTofWmByzYaf5VyMIF+w/71M= -github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1/go.mod h1:YA9We4kTafu7mlMnUh3In6Q2wpg8fYN3ycgCKOK1TB8= -github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06 h1:uD1LcKwvEAqzDsgVChBudPqo5BhPxkj9AgylT5QCReo= -github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= +github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db h1:z++X4DdoX+aNlZNT1ZY4cykiFay4+f077pa0AG48SGg= +github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db/go.mod h1:ptt910z9KFfpVSIbSbXvTRR7tS19mxD7EtmVbbJi/WE= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808164224-787abccfbc9e h1:3vNpomyzv714Hgls5vn+fC0vgv8wUOSHepUl7PB5nUs= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808164224-787abccfbc9e/go.mod h1:ORVFiW/KNRY52lNjkGwnFWCxNVfE97bJG2jr2fetq0I= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= -github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI= github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ= github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= -github.com/mattn/go-ieproxy v0.0.3/go.mod h1:6ZpRmhBaYuBX1U2za+9rC9iCGLsSp2tftelZne7CPko= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= +github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= +github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= @@ -474,56 +178,26 @@ github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= -go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= @@ -536,512 +210,118 @@ go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6b go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= -gocloud.dev v0.25.0 h1:Y7vDq8xj7SyM848KXf32Krda2e6jQ4CLh/mTeCSqXtk= -gocloud.dev v0.25.0/go.mod h1:7HegHVCYZrMiU3IE1qtnzf/vRrDwLYnRNR3EhWX8x9Y= +gocloud.dev v0.39.0 h1:EYABYGhAalPUaMrbSKOr5lejxoxvXj99nE8XFtsDgds= +gocloud.dev v0.39.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= +golang.org/x/oauth2 v0.22.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= -google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM= -google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.68.0/go.mod h1:sOM8pTpwgflXRhz+oC8H2Dr+UcbMqkPPWNJo88Q7TH8= -google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7C80= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.176.0 h1:dHj1/yv5Dm/eQTXiP9hNCRT3xzJHWXeNdRq29XbMxoE= -google.golang.org/api v0.176.0/go.mod h1:Rra+ltKu14pps/4xTycZfobMgLpbosoaaL7c+SEMrO8= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk= +google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220204002441-d6cc3cc0770e/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220216160803-4663080d8bc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= -google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 h1:+/tmTy5zAieooKIXfzDm9KiA3Bv6JBwriRN9LY+yayk= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988/go.mod h1:4+X6GvPs+25wZKbQq9qyAXrwIRExv7w0Ea6MgZLZiDM= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 h1:V71AcdLZr2p8dC9dbOIMCpqi4EmRl8wUwnJzXXLmbmc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1050,39 +330,26 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 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.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc= k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo= k8s.io/apiserver v0.31.0 h1:p+2dgJjy+bk+B1Csz+mc2wl5gHwvNkC9QJV+w55LVrY= @@ -1091,10 +358,6 @@ k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= From a86ded2438215c8c6777456f2bc9e9120baab3f9 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:22:45 -0400 Subject: [PATCH 210/229] Chore: Add apistore dependency (#92240) --- go.mod | 54 ++++---- go.sum | 360 ++++++++++++++-------------------------------------- go.work.sum | 17 +-- 3 files changed, 127 insertions(+), 304 deletions(-) diff --git a/go.mod b/go.mod index 16cf9f44dfa..02076abcdce 100644 --- a/go.mod +++ b/go.mod @@ -13,13 +13,13 @@ replace github.com/prometheus/prometheus => github.com/prometheus/prometheus v0. require ( buf.build/gen/go/parca-dev/parca/bufbuild/connect-go v1.10.0-20240523185345-933eab74d046.1 // @grafana/observability-traces-and-profiling buf.build/gen/go/parca-dev/parca/protocolbuffers/go v1.34.1-20240523185345-933eab74d046.1 // @grafana/observability-traces-and-profiling - cloud.google.com/go/kms v1.15.7 // @grafana/grafana-backend-group - cloud.google.com/go/storage v1.38.0 // @grafana/grafana-backend-group + cloud.google.com/go/kms v1.18.5 // @grafana/grafana-backend-group + cloud.google.com/go/storage v1.43.0 // @grafana/grafana-backend-group cuelang.org/go v0.6.0-0.dev // @grafana/grafana-as-code filippo.io/age v1.1.1 // @grafana/identity-access-team github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // @grafana/partner-datasources github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 // @grafana/grafana-backend-group - github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 // @grafana/grafana-backend-group + github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 // @grafana/grafana-backend-group github.com/Azure/azure-storage-blob-go v0.15.0 // @grafana/grafana-backend-group github.com/Azure/go-autorest/autorest v0.11.29 // @grafana/grafana-backend-group github.com/Azure/go-autorest/autorest/adal v0.9.23 // @grafana/grafana-backend-group @@ -34,7 +34,7 @@ require ( github.com/andybalholm/brotli v1.0.6 // @grafana/partner-datasources github.com/apache/arrow/go/v15 v15.0.2 // @grafana/observability-metrics github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad - github.com/aws/aws-sdk-go v1.51.31 // @grafana/aws-datasources + github.com/aws/aws-sdk-go v1.55.5 // @grafana/aws-datasources github.com/beevik/etree v1.2.0 // @grafana/grafana-backend-group github.com/benbjohnson/clock v1.3.5 // @grafana/alerting-backend github.com/blang/semver/v4 v4.0.0 // indirect; @grafana/grafana-release-guild @@ -70,8 +70,8 @@ require ( 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/uuid v1.6.0 // @grafana/grafana-backend-group - github.com/google/wire v0.5.0 // @grafana/grafana-backend-group - github.com/googleapis/gax-go/v2 v2.12.3 // @grafana/grafana-backend-group + github.com/google/wire v0.6.0 // @grafana/grafana-backend-group + github.com/googleapis/gax-go/v2 v2.13.0 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad github.com/grafana/alerting v0.0.0-20240812131556-611a23ff0f7f // @grafana/alerting-backend @@ -91,8 +91,8 @@ require ( github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group github.com/grafana/grafana-plugin-sdk-go v0.244.0 // @grafana/plugins-platform-backend github.com/grafana/grafana/pkg/aggregator v0.0.0-20240813192817-1b0e6b5c09b2 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240808213237-f4d2e064f435 // @grafana/grafana-app-platform-squad - github.com/grafana/grafana/pkg/apiserver v0.0.0-20240708134731-e9876749d440 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da // @grafana/grafana-app-platform-squad + github.com/grafana/grafana/pkg/apiserver v0.0.0-20240821155123-6891eb1d35da // @grafana/grafana-app-platform-squad // This needs to be here for other projects that import grafana/grafana // For local development grafana/grafana will always use the local files // Check go.work file for details @@ -171,7 +171,7 @@ require ( go.opentelemetry.io/otel/trace v1.28.0 // @grafana/grafana-backend-group go.uber.org/atomic v1.11.0 // @grafana/alerting-backend go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage - gocloud.dev v0.25.0 // @grafana/grafana-app-platform-squad + gocloud.dev v0.39.0 // @grafana/grafana-app-platform-squad golang.org/x/crypto v0.26.0 // @grafana/grafana-backend-group golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // @grafana/alerting-backend golang.org/x/mod v0.18.0 // indirect; @grafana/grafana-backend-group @@ -179,10 +179,10 @@ require ( golang.org/x/oauth2 v0.22.0 // @grafana/identity-access-team golang.org/x/sync v0.8.0 // @grafana/alerting-backend golang.org/x/text v0.17.0 // @grafana/grafana-backend-group - golang.org/x/time v0.5.0 // @grafana/grafana-backend-group + golang.org/x/time v0.6.0 // @grafana/grafana-backend-group golang.org/x/tools v0.22.0 // @grafana/grafana-as-code gonum.org/v1/gonum v0.14.0 // @grafana/observability-metrics - google.golang.org/api v0.176.0 // @grafana/grafana-backend-group + google.golang.org/api v0.191.0 // @grafana/grafana-backend-group google.golang.org/grpc v1.65.0 // @grafana/plugins-platform-backend google.golang.org/protobuf v1.34.2 // @grafana/plugins-platform-backend gopkg.in/ini.v1 v1.67.0 // @grafana/alerting-backend @@ -204,15 +204,15 @@ require ( ) require ( - cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/auth v0.2.2 // indirect - cloud.google.com/go/auth/oauth2adapt v0.2.1 // indirect - cloud.google.com/go/compute/metadata v0.3.0 // indirect - cloud.google.com/go/iam v1.1.6 // indirect + cloud.google.com/go v0.115.0 // indirect + cloud.google.com/go/auth v0.8.1 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect + cloud.google.com/go/compute/metadata v0.5.0 // indirect + cloud.google.com/go/iam v1.1.13 // indirect github.com/Azure/azure-pipeline-go v0.2.3 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect - github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect @@ -236,10 +236,6 @@ require ( github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go-v2 v1.30.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 // indirect - github.com/aws/smithy-go v1.20.3 // indirect github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 // indirect @@ -303,9 +299,10 @@ require ( github.com/google/flatbuffers v24.3.25+incompatible // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/s2a-go v0.1.7 // indirect + github.com/google/s2a-go v0.1.8 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240624122844-a89deaeb7365 // @grafana/grafana-search-and-storage + github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20240821183201-2f012860344d // @grafana/grafana-search-and-storage + github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240821161612-71f0dae39e9d // @grafana/grafana-search-and-storage github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db // indirect github.com/grafana/sqlds/v3 v3.2.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect @@ -445,10 +442,10 @@ require ( go.uber.org/zap v1.27.0 // @grafana/identity-access-team golang.org/x/sys v0.24.0 // indirect golang.org/x/term v0.23.0 // indirect - golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect; @grafana/grafana-backend-group - google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect + google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect; @grafana/grafana-backend-group + google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -481,6 +478,7 @@ require ( ) require ( + cloud.google.com/go/longrunning v0.5.12 // indirect github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435 // indirect github.com/hairyhenderson/go-which v0.2.0 // indirect github.com/iancoleman/orderedmap v0.3.0 // indirect diff --git a/go.sum b/go.sum index e8e11c8d242..5d1fa517353 100644 --- a/go.sum +++ b/go.sum @@ -25,7 +25,6 @@ cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPT cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.82.0/go.mod h1:vlKccHJGuFBFufnAnuB08dfEH9Y3H7dzDzRECFdC2TA= cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= @@ -51,8 +50,8 @@ cloud.google.com/go v0.110.9/go.mod h1:rpxevX/0Lqvlbc88b7Sc1SPNdyK1riNBTUU6JXhYN cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic= cloud.google.com/go v0.111.0/go.mod h1:0mibmpKP1TyOOFYQY5izo0LnT+ecvOQ0Sg3OdmMiNRU= cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4= -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= +cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14= +cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU= cloud.google.com/go/accessapproval v1.4.0/go.mod h1:zybIuC3KpDOvotz59lFe5qxRZx6C75OtwbisN56xYB4= cloud.google.com/go/accessapproval v1.5.0/go.mod h1:HFy3tuiGvMdcd/u+Cu5b9NkO1pEICJ46IR82PoUdplw= cloud.google.com/go/accessapproval v1.6.0/go.mod h1:R0EiYnwV5fsRFiKZkPHr6mwyk2wxUJ30nL4j2pcFY2E= @@ -190,11 +189,11 @@ cloud.google.com/go/assuredworkloads v1.11.3/go.mod h1:vEjfTKYyRUaIeA0bsGJceFV2J cloud.google.com/go/assuredworkloads v1.11.4/go.mod h1:4pwwGNwy1RP0m+y12ef3Q/8PaiWrIDQ6nD2E8kvWI9U= cloud.google.com/go/assuredworkloads v1.11.5/go.mod h1:FKJ3g3ZvkL2D7qtqIGnDufFkHxwIpNM9vtmhvt+6wqk= cloud.google.com/go/auth v0.2.0/go.mod h1:+yb+oy3/P0geX6DLKlqiGHARGR6EX2GRtYCzWOCQSbU= -cloud.google.com/go/auth v0.2.2 h1:gmxNJs4YZYcw6YvKRtVBaF2fyUE6UrWPyzU8jHvYfmI= -cloud.google.com/go/auth v0.2.2/go.mod h1:2bDNJWtWziDT3Pu1URxHHbkHE/BbOCuyUiKIGcNvafo= +cloud.google.com/go/auth v0.8.1 h1:QZW9FjC5lZzN864p13YxvAtGUlQ+KgRL+8Sg45Z6vxo= +cloud.google.com/go/auth v0.8.1/go.mod h1:qGVp/Y3kDRSDZ5gFD/XPUfYQ9xW1iI7q8RIRoCyBbJc= cloud.google.com/go/auth/oauth2adapt v0.2.0/go.mod h1:AfqujpDAlTfLfeCIl/HJZZlIxD8+nJoZ5e0x1IxGq5k= -cloud.google.com/go/auth/oauth2adapt v0.2.1 h1:VSPmMmUlT8CkIZ2PzD9AlLN+R3+D1clXMWHHa6vG/Ag= -cloud.google.com/go/auth/oauth2adapt v0.2.1/go.mod h1:tOdK/k+D2e4GEwfBRA48dKNQiDsqIXxLh7VU319eV0g= +cloud.google.com/go/auth/oauth2adapt v0.2.4 h1:0GWE/FUsXhf6C+jAkWgYm7X9tK8cuEIfy19DBn6B6bY= +cloud.google.com/go/auth/oauth2adapt v0.2.4/go.mod h1:jC/jOpwFP6JBxhB3P5Rr0a9HLMC/Pe3eaL4NmdvqPtc= cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= cloud.google.com/go/automl v1.7.0/go.mod h1:RL9MYCCsJEOmt0Wf3z9uzG0a7adTT1fe+aObgSpkCt8= @@ -335,7 +334,6 @@ cloud.google.com/go/cloudtasks v1.12.3/go.mod h1:GPVXhIOSGEaR+3xT4Fp72ScI+HjHffS cloud.google.com/go/cloudtasks v1.12.4/go.mod h1:BEPu0Gtt2dU6FxZHNqqNdGqIG86qyWKBPGnsb7udGY0= cloud.google.com/go/cloudtasks v1.12.6/go.mod h1:b7c7fe4+TJsFZfDyzO51F7cjq7HLUlRi/KZQLQjDsaY= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= @@ -363,8 +361,9 @@ cloud.google.com/go/compute/metadata v0.1.0/go.mod h1:Z1VN+bulIf6bt4P/C37K4DyZYZ cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= cloud.google.com/go/compute/metadata v0.2.1/go.mod h1:jgHgmJd2RKBGzXqF5LR2EZMGxBkeanZ9wwa75XHJgOM= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY= +cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY= cloud.google.com/go/contactcenterinsights v1.3.0/go.mod h1:Eu2oemoePuEFc/xKFPjbTuPSj0fYJcPls9TFlPNnHHY= cloud.google.com/go/contactcenterinsights v1.4.0/go.mod h1:L2YzkGbPsv+vMQMCADxJoT9YiTTnSEd6fEvCeHTYVck= cloud.google.com/go/contactcenterinsights v1.6.0/go.mod h1:IIDlT6CLcDoyv79kDv8iWxMSTZhLxSCofVV5W6YFM/w= @@ -685,7 +684,6 @@ cloud.google.com/go/gsuiteaddons v1.6.3/go.mod h1:sCFJkZoMrLZT3JTb8uJqgKPNshH2tf cloud.google.com/go/gsuiteaddons v1.6.4/go.mod h1:rxtstw7Fx22uLOXBpsvb9DUbC+fiXs7rF4U29KHM/pE= cloud.google.com/go/gsuiteaddons v1.6.5/go.mod h1:Lo4P2IvO8uZ9W+RaC6s1JVxo42vgy+TX5a6hfBZ0ubs= cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= -cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw= cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= cloud.google.com/go/iam v0.6.0/go.mod h1:+1AH33ueBne5MzYccyMHtEKqLE4/kJOibtffMHDMFMc= @@ -701,8 +699,9 @@ cloud.google.com/go/iam v1.1.2/go.mod h1:A5avdyVL2tCppe4unb0951eI9jreack+RJ0/d+K cloud.google.com/go/iam v1.1.3/go.mod h1:3khUlaBXfPKKe7huYgEpDn6FtgRyMEqbkvBxrQyY5SE= cloud.google.com/go/iam v1.1.4/go.mod h1:l/rg8l1AaA+VFMho/HYx2Vv6xinPSLMF8qfhRPIZ0L8= cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8= -cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= +cloud.google.com/go/iam v1.1.13 h1:7zWBXG9ERbMLrzQBRhFliAV+kjcRToDTgQT3CTwYyv4= +cloud.google.com/go/iam v1.1.13/go.mod h1:K8mY0uSXwEXS30KrnVb+j54LB/ntfZu1dr+4zFMNbus= cloud.google.com/go/iap v1.4.0/go.mod h1:RGFwRJdihTINIe4wZ2iCP0zF/qu18ZwyKxrhMhygBEc= cloud.google.com/go/iap v1.5.0/go.mod h1:UH/CGgKd4KyohZL5Pt0jSKE4m3FR51qg6FKQ/z/Ix9A= cloud.google.com/go/iap v1.6.0/go.mod h1:NSuvI9C/j7UdjGjIde7t7HBz+QTwBcapPE07+sSRcLk= @@ -731,7 +730,6 @@ cloud.google.com/go/iot v1.7.2/go.mod h1:q+0P5zr1wRFpw7/MOgDXrG/HVA+l+cSwdObffkr cloud.google.com/go/iot v1.7.3/go.mod h1:t8itFchkol4VgNbHnIq9lXoOOtHNR3uAACQMYbN9N4I= cloud.google.com/go/iot v1.7.4/go.mod h1:3TWqDVvsddYBG++nHSZmluoCAVGr1hAcabbWZNKEZLk= cloud.google.com/go/iot v1.7.5/go.mod h1:nq3/sqTz3HGaWJi1xNiX7F41ThOzpud67vwk0YsSsqs= -cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI= cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= cloud.google.com/go/kms v1.5.0/go.mod h1:QJS2YY0eJGBg3mnDfuaCyLauWwBJiHRboYxJ++1xJNg= cloud.google.com/go/kms v1.6.0/go.mod h1:Jjy850yySiasBUDi6KFUwUv2n1+o7QZFyuUJg6OgjA0= @@ -747,8 +745,9 @@ cloud.google.com/go/kms v1.15.3/go.mod h1:AJdXqHxS2GlPyduM99s9iGqi2nwbviBbhV/hdm cloud.google.com/go/kms v1.15.4/go.mod h1:L3Sdj6QTHK8dfwK5D1JLsAyELsNMnd3tAIwGS4ltKpc= cloud.google.com/go/kms v1.15.5/go.mod h1:cU2H5jnp6G2TDpUGZyqTCoy1n16fbubHZjmVXSMtwDI= cloud.google.com/go/kms v1.15.6/go.mod h1:yF75jttnIdHfGBoE51AKsD/Yqf+/jICzB9v1s1acsms= -cloud.google.com/go/kms v1.15.7 h1:7caV9K3yIxvlQPAcaFffhlT7d1qpxjB1wHBtjWa13SM= cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI= +cloud.google.com/go/kms v1.18.5 h1:75LSlVs60hyHK3ubs2OHd4sE63OAMcM2BdSJc2bkuM4= +cloud.google.com/go/kms v1.18.5/go.mod h1:yXunGUGzabH8rjUPImp2ndHiGolHeWJJ0LODLedicIY= cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= cloud.google.com/go/language v1.7.0/go.mod h1:DJ6dYN/W+SQOjF8e1hLQXMF21AkH2w9wiPzPCJa2MIE= @@ -782,6 +781,8 @@ cloud.google.com/go/longrunning v0.5.2/go.mod h1:nqo6DQbNV2pXhGDbDMoN2bWz68MjZUz cloud.google.com/go/longrunning v0.5.3/go.mod h1:y/0ga59EYu58J6SHmmQOvekvND2qODbu8ywBBW7EK7Y= cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI= cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s= +cloud.google.com/go/longrunning v0.5.12 h1:5LqSIdERr71CqfUsFlJdBpOkBH8FBCFD7P1nTWy3TYE= +cloud.google.com/go/longrunning v0.5.12/go.mod h1:S5hMV8CDJ6r50t2ubVJSKQVv5u0rmik5//KgLO3k4lU= cloud.google.com/go/managedidentities v1.3.0/go.mod h1:UzlW3cBOiPrzucO5qWkNkh0w33KFtBJU281hacNvsdE= cloud.google.com/go/managedidentities v1.4.0/go.mod h1:NWSBYbEMgqmbZsLIyKvxrYbtqOsxY1ZrGM+9RgDqInM= cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= @@ -831,8 +832,6 @@ cloud.google.com/go/metastore v1.13.1/go.mod h1:IbF62JLxuZmhItCppcIfzBBfUFq0DIB9 cloud.google.com/go/metastore v1.13.2/go.mod h1:KS59dD+unBji/kFebVp8XU/quNSyo8b6N6tPGspKszA= cloud.google.com/go/metastore v1.13.3/go.mod h1:K+wdjXdtkdk7AQg4+sXS8bRrQa9gcOr+foOMF2tqINE= cloud.google.com/go/metastore v1.13.4/go.mod h1:FMv9bvPInEfX9Ac1cVcRXp8EBBQnBcqH6gz3KvJ9BAE= -cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4= -cloud.google.com/go/monitoring v1.4.0/go.mod h1:y6xnxfwI3hTFWOdkOaD7nfJVlwuC3/mS/5kvtT131p4= cloud.google.com/go/monitoring v1.7.0/go.mod h1:HpYse6kkGo//7p6sT0wsIC6IBDET0RhIsnmlA53dvEk= cloud.google.com/go/monitoring v1.8.0/go.mod h1:E7PtoMJ1kQXWxPjB6mv2fhC5/15jInuulFdYYtlcvT4= cloud.google.com/go/monitoring v1.12.0/go.mod h1:yx8Jj2fZNEkL/GYZyTLS4ZtZEZN8WtDEiEqG4kLK50w= @@ -970,7 +969,6 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.19.0/go.mod h1:/O9kmSe9bb9KRnIAWkzmqhPjHo6LtzGOBYd/kr06XSs= cloud.google.com/go/pubsub v1.26.0/go.mod h1:QgBH3U/jdJy/ftjPhTkyXNj543Tin1pRYcdcPRnFIRI= cloud.google.com/go/pubsub v1.27.1/go.mod h1:hQN39ymbV9geqBnfQq6Xf63yNhUAhv9CZhzp5O6qsW0= cloud.google.com/go/pubsub v1.28.0/go.mod h1:vuXFpwaVoIPQMGXqRyUQigu/AX1S3IWugR9xznmcXX8= @@ -1080,7 +1078,6 @@ cloud.google.com/go/scheduler v1.10.3/go.mod h1:8ANskEM33+sIbpJ+R4xRfw/jzOG+ZFE8 cloud.google.com/go/scheduler v1.10.4/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= cloud.google.com/go/scheduler v1.10.5/go.mod h1:MTuXcrJC9tqOHhixdbHDFSIuh7xZF2IysiINDuiq6NI= cloud.google.com/go/scheduler v1.10.6/go.mod h1:pe2pNCtJ+R01E06XCDOJs1XvAMbv28ZsQEbqknxGOuE= -cloud.google.com/go/secretmanager v1.3.0/go.mod h1:+oLTkouyiYiabAQNugCeTS3PAArGiMJuBqvJnJsyH+U= cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= cloud.google.com/go/secretmanager v1.8.0/go.mod h1:hnVgi/bN5MYHd3Gt0SPuTPPp5ENina1/LxM+2W9U9J4= cloud.google.com/go/secretmanager v1.9.0/go.mod h1:b71qH2l1yHmWQHt9LC80akm86mX8AL6X1MA01dW8ht4= @@ -1179,7 +1176,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA= cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= @@ -1188,8 +1184,8 @@ cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjp cloud.google.com/go/storage v1.30.1/go.mod h1:NfxhC0UJE1aXSx7CIIbCf7y9HKT7BiccwkR7+P7gN8E= cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8= cloud.google.com/go/storage v1.37.0/go.mod h1:i34TiT2IhiNDmcj65PqwCjcoUX7Z5pLzS8DEmoiFq1k= -cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= -cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= +cloud.google.com/go/storage v1.43.0 h1:CcxnSohZwizt4LCzQHWvBf1/kvtHUn7gk9QERXPyXFs= +cloud.google.com/go/storage v1.43.0/go.mod h1:ajvxEa7WmZS1PxvKRq4bq0tFT3vMd502JwstCcYv0Q0= cloud.google.com/go/storagetransfer v1.5.0/go.mod h1:dxNzUopWy7RQevYFHewchb29POFv3/AaBgnhqzqiK0w= cloud.google.com/go/storagetransfer v1.6.0/go.mod h1:y77xm4CQV/ZhFZH75PLEXY0ROiS7Gh6pSKrM8dJyg6I= cloud.google.com/go/storagetransfer v1.7.0/go.mod h1:8Giuj1QNb1kfLAiWM1bN6dHzfdlDAVC9rv9abHot2W4= @@ -1225,8 +1221,6 @@ cloud.google.com/go/tpu v1.6.2/go.mod h1:NXh3NDwt71TsPZdtGWgAG5ThDfGd32X1mJ2cMaR cloud.google.com/go/tpu v1.6.3/go.mod h1:lxiueqfVMlSToZY1151IaZqp89ELPSrk+3HIQ5HRkbY= cloud.google.com/go/tpu v1.6.4/go.mod h1:NAm9q3Rq2wIlGnOhpYICNI7+bpBebMJbh0yyp3aNw1Y= cloud.google.com/go/tpu v1.6.5/go.mod h1:P9DFOEBIBhuEcZhXi+wPoVy/cji+0ICFi4TtTkMHSSs= -cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A= -cloud.google.com/go/trace v1.2.0/go.mod h1:Wc8y/uYyOhPy12KEnXG9XGrvfMz5F5SrYecQlbW1rwM= cloud.google.com/go/trace v1.3.0/go.mod h1:FFUE83d9Ca57C+K8rDl/Ih8LwOzWIV1krKgxg6N0G28= cloud.google.com/go/trace v1.4.0/go.mod h1:UG0v8UBqzusp+z63o7FK74SdFE+AXpCLdFb1rshXG+Y= cloud.google.com/go/trace v1.8.0/go.mod h1:zH7vcsbAhklH8hWFig58HvxcxyQbaIqMarMg9hn5ECA= @@ -1340,9 +1334,6 @@ cloud.google.com/go/workflows v1.12.1/go.mod h1:5A95OhD/edtOhQd/O741NSfIMezNTbCw cloud.google.com/go/workflows v1.12.2/go.mod h1:+OmBIgNqYJPVggnMo9nqmizW0qEXHhmnAzK/CnBqsHc= cloud.google.com/go/workflows v1.12.3/go.mod h1:fmOUeeqEwPzIU81foMjTRQIdwQHADi/vEr1cx9R1m5g= cloud.google.com/go/workflows v1.12.4/go.mod h1:yQ7HUqOkdJK4duVtMeBCAOPiN1ZF1E9pAMX51vpwB/w= -contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= -contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8= -contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= @@ -1350,15 +1341,10 @@ filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg= filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= git.sr.ht/~sbinet/gg v0.3.1/go.mod h1:KGYtlADtqsqANL9ueOFkWymvzUvLMQllU5Ixo+8v3pc= -github.com/Azure/azure-amqp-common-go/v3 v3.2.1/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI= -github.com/Azure/azure-amqp-common-go/v3 v3.2.2/go.mod h1:O6X1iYHP7s2x7NjUKsXVhkwWrQhxrd+d8/3rRadj4CI= github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= -github.com/Azure/azure-sdk-for-go v51.1.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v59.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.0.0/go.mod h1:uGG2W01BaETf0Ozp+QxxKJdMBNRWPdstHG0Fmdwn1/U= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.4.0/go.mod h1:ON4tFdPTwRcgWEaVDrN3584Ef+b7GgSJaXxe5fW9t4M= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.8.0/go.mod h1:3Ug6Qzto9anB6mGlEdgYMDF5zHQ+wwhEaYR4s17PHMw= @@ -1366,16 +1352,14 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.0/go.mod h1:uReU2sSxZExRPBAg3q github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2/go.mod h1:5FDJtLEO/GxwNgUxbwrY3LP0pEoThTQJtk2oysdXHxM= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.0/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.0.0/go.mod h1:+6sju8gk8FRmSajX3Oz4G5Gm7P+mbqE9FVaXXFYTkCM= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.2.0/go.mod h1:NBanQUfSWiWn3QEpWDTCU0IjBECKOYvl2R8xdRtMtiM= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.4.0/go.mod h1:1fXstnBMas5kzG+S3q8UoJcmyU6nUeunJcMDHcRYHhs= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.2/go.mod h1:aiYBYui4BJ/BJCAIKs92XiPyQfTaBWqvHujDwKb6CBU= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= -github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.0.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= github.com/Azure/azure-sdk-for-go/sdk/internal v1.1.2/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= github.com/Azure/azure-sdk-for-go/sdk/internal v1.2.0/go.mod h1:eWRD7oawr1Mu1sLCawqVc0CUiF43ia3qQMxLscsKQ9w= @@ -1384,10 +1368,10 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.0/go.mod h1:s4kgfzA0covAXNic github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0 h1:TOFrNxfjslms5nLLIMjW7N0+zSALX4KiGsptmpb16AA= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.9.0/go.mod h1:EAyXOW1F6BTJPiK2pDvmnvxOHPxoTYWoqBeIlql+QhI= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0 h1:Lg6BW0VPmCwcMlvOviL3ruHFO+H9tZNqscK0AeuFjGM= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.0/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= +github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.6.0 h1:ui3YNbxfW7J3tTFIZMH6LIGRjCngp+J+nIFlnizfNTE= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.6.0/go.mod h1:gZmgV+qBqygoznvqo2J9oKZAFziqhLZ2xE/WVUmzkHA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2/go.mod h1:FbdwsQ2EzwvXxOPcMFYO8ogEc9uMMIj3YkmCdXdAFmk= @@ -1401,32 +1385,20 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= -github.com/Azure/azure-service-bus-go v0.11.5/go.mod h1:MI6ge2CuQWBVq+ly456MY7XqNLJip5LO1iSFodbNLbU= -github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7OgcSXPpwp3tx6qk= github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58= -github.com/Azure/go-amqp v0.16.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= -github.com/Azure/go-amqp v0.16.4/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.19/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= -github.com/Azure/go-autorest/autorest v0.11.22/go.mod h1:BAWYUWGPEtKPzjVkp0Q6an0MJcJDsoh5Z1BFAEFs4Xs= github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= github.com/Azure/go-autorest/autorest v0.11.29/go.mod h1:ZtEzC4Jy2JDrZLxvWs8LrBWEBycl1hbT1eknI8MtfAs= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.14/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= -github.com/Azure/go-autorest/autorest/adal v0.9.17/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= github.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8= github.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c= -github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= @@ -1458,7 +1430,6 @@ github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/FZambia/eagle v0.1.0 h1:9gyX6x+xjoIfglgyPTcYm7dvY7FJ93us1QY5De4CyXA= github.com/FZambia/eagle v0.1.0/go.mod h1:YjGSPVkQTNcVLfzEUQJNgW9ScPR0K4u/Ky0yeFa4oDA= -github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0/go.mod h1:spvB9eLJH9dutlbPSRmHvSXXHOwGRyeXh1jVdquA2G8= github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= github.com/HdrHistogram/hdrhistogram-go v1.1.2 h1:5IcZpTvzydCQeHzK4Ef/D5rrSqwxob0t8PQPMybUNFM= github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= @@ -1572,65 +1543,54 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= -github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go v1.50.29/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go v1.51.25/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/aws/aws-sdk-go v1.51.31 h1:4TM+sNc+Dzs7wY1sJ0+J8i60c6rkgnKP1pvPx8ghsSY= -github.com/aws/aws-sdk-go v1.51.31/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= +github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= -github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= -github.com/aws/aws-sdk-go-v2 v1.30.1 h1:4y/5Dvfrhd1MxRDD77SrfsDaj8kUkkljU7XE83NPV+o= -github.com/aws/aws-sdk-go-v2 v1.30.1/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM= -github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg= -github.com/aws/aws-sdk-go-v2/config v1.24.0 h1:4LEk29JO3w+y9dEo/5Tq5QTP7uIEw+KQrKiHOs4xlu4= -github.com/aws/aws-sdk-go-v2/config v1.24.0/go.mod h1:11nNDAuK86kOUHeuEQo8f3CkcV5xuUxvPwFjTZE/PnQ= -github.com/aws/aws-sdk-go-v2/credentials v1.11.2 h1:RQQ5fzclAKJyY5TvF+fkjJEwzK4hnxQCLOu5JXzDmQo= -github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 h1:LWPg5zjHV9oz/myQr4wMs0gi4CjnDN/ILmyZUFYXZsU= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3 h1:ir7iEq78s4txFGgwcLqD6q9IIPzTQNRJXulJd9h/zQo= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13 h1:5SAoZ4jYpGH4721ZNoS1znQrhOfZinOhc4XuTXx/nVc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.13/go.mod h1:+rdA6ZLpaSeM7tSg/B0IEDinCIBJGmW8rKDFkYpP04g= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13 h1:WIijqeaAO7TYFLbhsZmi2rgLEAtWOC1LhxCAVTJlSKw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.13/go.mod h1:i+kbfa76PQbWw/ULoWnp51EYVWH4ENln76fLQE3lXT8= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 h1:by9P+oy3P/CwggN4ClnW2D4oL91QV7pBzBICi1chZvQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE= +github.com/aws/aws-sdk-go-v2 v1.30.3 h1:jUeBtG0Ih+ZIFH0F4UkmL9w3cSpaMv9tYYDbzILP8dY= +github.com/aws/aws-sdk-go-v2 v1.30.3/go.mod h1:nIQjQVp5sfpQcTc9mPSr1B0PaWK5ByX9MOoDadSN4lc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3 h1:tW1/Rkad38LA15X4UQtjXZXNKsCgkshC3EbmcUmghTg= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.3/go.mod h1:UbnqO+zjqk3uIt9yCACHJ9IVNhyhOCnYk8yA19SAWrM= +github.com/aws/aws-sdk-go-v2/config v1.27.27 h1:HdqgGt1OAP0HkEDDShEl0oSYa9ZZBSOmKpdpsDMdO90= +github.com/aws/aws-sdk-go-v2/config v1.27.27/go.mod h1:MVYamCg76dFNINkZFu4n4RjDixhVr51HLj4ErWzrVwg= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27 h1:2raNba6gr2IfA0eqqiP2XiQ0UVOpGPgDSi0I9iAP+UI= +github.com/aws/aws-sdk-go-v2/credentials v1.17.27/go.mod h1:gniiwbGahQByxan6YjQUMcW4Aov6bLC3m+evgcoN4r4= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11 h1:KreluoV8FZDEtI6Co2xuNk/UqI9iwMrOx/87PBNIKqw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.11/go.mod h1:SeSUYBLsMYFoRvHE0Tjvn7kbxaUhl75CJi1sbfhMxkU= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10 h1:zeN9UtUlA6FTx0vFSayxSX32HDw73Yb6Hh2izDSFxXY= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.10/go.mod h1:3HKuexPDcwLWPaqpW2UR/9n8N/u/3CKcGAzSs8p8u8g= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15 h1:SoNJ4RlFEQEbtDcCEt+QG56MY4fm4W8rYirAmq+/DdU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.15/go.mod h1:U9ke74k1n2bf+RIgoX1SXFed1HLs51OgUSs+Ph0KJP8= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15 h1:C6WHdGnTDIYETAm5iErQUiVNsclNx9qbJVPIt03B6bI= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.15/go.mod h1:ZQLZqhcu+JhSrA9/NXRm8SkDvsycE+JkV3WGY41e+IM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15 h1:Z5r7SycxmSllHYmaAZPpmN8GviDrSGhMS6bldqtXZPw= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.15/go.mod h1:CetW7bDE00QoGEmPUoZuRog07SGVAUVW6LFpNP0YfIg= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 h1:T4pFel53bkHjL2mMo+4DKE6r6AuoZnM0fg7k1/ratr4= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3 h1:I0dcwWitE752hVSMrsLCxqNQ+UdEp3nACx2bYNMQq+k= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 h1:Gh1Gpyh01Yvn7ilO/b/hr01WgNpaszfbKMUgqM186xQ= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3 h1:BKjwCJPnANbkwQ8vzSbaZDKawwagDubrH/z/c0X+kbQ= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc= -github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3 h1:rMPtwA7zzkSQZhhz9U3/SoIDz/NZ7Q+iRn4EIO8rSyU= -github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o= -github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw= -github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM= -github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI+N/H1I1ynHwXnO6FowXJc0= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 h1:frW4ikGcxfAEDfmQqWgMLp+F1n4nRo9sF39OcIb5BkQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2 h1:ORnrOK0C4WmYV/uYt3koHEWBLYsRDwk2Np+eEoyV4Z0= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.2/go.mod h1:xyFHA4zGxgYkdD73VeezHt3vSKEG9EmFnGwoKlP00u4= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 h1:cJGRyzCSVwZC7zZZ1xbx9m32UnrKydRYhOvcD1NYP9Q= -github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3 h1:dT3MqvGhSoaIhRseqw2I0yH81l7wiR2vjs57O51EAm8= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.3/go.mod h1:GlAeCkHwugxdHaueRr4nhPuY+WW+gR8UjlcqzPr1SPI= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17 h1:YPYe6ZmvUfDDDELqEKtAd6bo8zxhkm+XEFEzQisqUIE= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.3.17/go.mod h1:oBtcnYua/CgzCWYN7NZ5j7PotFDaFSUjCYVTtfyn7vw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17 h1:HGErhhrxZlQ044RiM+WdoZxp0p+EGM62y3L6pwA4olE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.17/go.mod h1:RkZEx4l0EHYDJpWppMJ3nD9wZJAa8/0lq9aVC+r2UII= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15 h1:246A4lSTXWJw/rmlQI+TT2OcqeDMKBdyjEQrafMaQdA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.17.15/go.mod h1:haVfg3761/WF7YPuJOER2MP0k4UAXyHaLclKXB6usDg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3 h1:hT8ZAZRIfqBqHbzKTII+CIiY8G2oC9OpLedkZ51DWl8= +github.com/aws/aws-sdk-go-v2/service/s3 v1.58.3/go.mod h1:Lcxzg5rojyVPU/0eFwLtcyTaek/6Mtic5B1gJo7e/zE= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4 h1:BXx0ZIxvrJdSgSvKTZ+yRBeSqqgPM89VPlulEcl37tM= +github.com/aws/aws-sdk-go-v2/service/sso v1.22.4/go.mod h1:ooyCOXjvJEsUw7x+ZDHeISPMhtwI3ZCB7ggFMcFfWLU= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4 h1:yiwVzJW2ZxZTurVbYWA7QOrAaCYQR72t0wrSBfoesUE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.26.4/go.mod h1:0oxfLkpz3rQ/CHlx5hB7H69YUpFiI1tql6Q6Ne+1bCw= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 h1:ZsDKRLXGWHk8WdtyYMoGNO7bTudrvuKpDKgMVRlepGE= +github.com/aws/aws-sdk-go-v2/service/sts v1.30.3/go.mod h1:zwySh8fpFyXp9yOr/KVzxOl8SRqgf/IDw5aUt9UKFcQ= github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f h1:y06x6vGnFYfXUoVMbrcP1Uzpj4JG01eB5vRps9G8agM= @@ -1781,8 +1741,6 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -1822,10 +1780,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs 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/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= -github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= github.com/dennwc/varint v1.0.0 h1:kGNFFSSw8ToIy3obO/kKr8U9GZYUAxQEVuix4zfDWzE= github.com/dennwc/varint v1.0.0/go.mod h1:hnItb35rvZvJrbTALZtY/iQfDs48JKRG1RPpgziApxA= -github.com/devigned/tab v0.1.1/go.mod h1:XG9mPq0dFghrYvoBF3xdRrJzSTX1b7IQrvaL9mzjeJY= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-metro v0.0.0-20180109044635-280f6062b5bc/go.mod h1:c9O8+fpSOX1DM8cPNSkX/qsBWdkD4yd2dpciOWQjpBw= github.com/dgryski/go-metro v0.0.0-20211217172704-adc40b04c140 h1:y7y0Oa6UawqTFPCDw9JG6pdKt4F9pAhHv0B7FMGaGD0= @@ -1835,8 +1791,6 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc= github.com/digitalocean/godo v1.113.0 h1:CLtCxlP4wDAjKIQ+Hshht/UNbgAp8/J/XBH1ZtDCF9Y= github.com/digitalocean/godo v1.113.0/go.mod h1:Z2mTP848Vi3IXXl5YbPekUgr4j4tOePomA+OE1Ag98w= -github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= -github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlmiddlecote/sqlstats v1.0.2 h1:gSU11YN23D/iY50A2zVYwgXgy072khatTsIW6UPjUtI= @@ -1924,7 +1878,6 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= @@ -1935,7 +1888,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= @@ -1949,9 +1901,6 @@ github.com/gchaincl/sqlhooks v1.3.0/go.mod h1:9BypXnereMT0+Ys8WGWHqzgkkOfHIhyeUC github.com/getkin/kin-openapi v0.125.0 h1:jyQCyf2qXS1qvs2U00xQzkGCqYPhEhZDmSmVt65fXno= github.com/getkin/kin-openapi v0.125.0/go.mod h1:wb1aSZA/iWmorQP9KTAS/phLj/t17B5jT7+fS8ed9NM= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= -github.com/gin-gonic/gin v1.7.3/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= @@ -1962,7 +1911,6 @@ github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmn github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -2043,7 +1991,6 @@ github.com/go-pdf/fpdf v0.6.0/go.mod h1:HzcnA+A23uwogo0tp9yU+l3V+KXhiESpt1PMayhO github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= @@ -2070,11 +2017,8 @@ github.com/go-zookeeper/zk v1.0.3 h1:7M2kwOsc//9VeeFiPtf+uSJlVpU66x9Ba5+8XK7/TDg github.com/go-zookeeper/zk v1.0.3/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= @@ -2084,7 +2028,6 @@ github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXK github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng= github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -2101,7 +2044,6 @@ github.com/gogo/status v1.1.1/go.mod h1:jpG3dM5QPcqu19Hg8lkUhBFBa3TcLs1DG7+2Jqci github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -2110,10 +2052,8 @@ github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17w github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnbAaq2zgrPrs= github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= @@ -2203,20 +2143,18 @@ github.com/google/go-pkcs11 v0.2.1-0.20230907215043-c6f79328ddf9/go.mod h1:6eQoG 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= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= -github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk= -github.com/google/go-replayers/httpreplay v1.1.1/go.mod h1:gN9GeLIs7l6NUoVaSSnv2RiqK1NiwAmD0MrKeC9IIks= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.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= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:xmapqc1AyLoB+ddYT6r04bD9lIjlOqGaREovi0SzFaE= -github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -2230,7 +2168,6 @@ github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210506205249-923b5ab0fc1a/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= @@ -2242,9 +2179,10 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4 github.com/google/s2a-go v0.1.0/go.mod h1:OJpEgntRZo8ugHpF9hkoLJbS5dSI20XZeXJ9JVywLlM= github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= github.com/google/s2a-go v0.1.4/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= -github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -2255,8 +2193,8 @@ github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8= -github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU= +github.com/google/wire v0.6.0 h1:HBkoIh4BdSxoyo9PveV8giw7ZsaBOvzWKfcg/6MrVwI= +github.com/google/wire v0.6.0/go.mod h1:F4QhpQ9EDIdJ1Mbop/NZBRB+5yrR6qg3BnctaoUk6NA= github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= @@ -2282,8 +2220,9 @@ github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2e github.com/googleapis/gax-go/v2 v2.11.0/go.mod h1:DxmR61SGKkGLa2xigwuZIQpkCI2S5iydzRfb3peWZJI= github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU= github.com/googleapis/gax-go/v2 v2.12.2/go.mod h1:61M8vcyyXR2kqKFxKrfA22jaA8JGF7Dc8App1U3H6jc= -github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s= +github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gophercloud/gophercloud v1.11.0 h1:ls0O747DIq1D8SUHc7r2vI8BFbMLeLFuENaAIfEx7OM= @@ -2302,18 +2241,15 @@ github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+ github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grafana/alerting v0.0.0-20240812131556-611a23ff0f7f h1:c8QAFXkilBiF29xc7oKO2IkbGE3bp9NIKgiNLazdooY= github.com/grafana/alerting v0.0.0-20240812131556-611a23ff0f7f/go.mod h1:DLj8frbtCaITljC2jc0L85JQViPF3mPfOSiYhm1osso= -github.com/grafana/authlib v0.0.0-20240814072707-6cffd53bb828 h1:+4kEyS1l0qXhg6sVH0W2ZNVYQ5GQiLnD8MlMUKvQ83o= -github.com/grafana/authlib v0.0.0-20240814072707-6cffd53bb828/go.mod h1:71+xJm0AE6eNGNExUvnABtyEztQ/Acb53/TAdOgwdmc= github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db h1:z++X4DdoX+aNlZNT1ZY4cykiFay4+f077pa0AG48SGg= github.com/grafana/authlib v0.0.0-20240814074258-eae7d47f01db/go.mod h1:ptt910z9KFfpVSIbSbXvTRR7tS19mxD7EtmVbbJi/WE= -github.com/grafana/authlib/claims v0.0.0-20240814072707-6cffd53bb828 h1:Hk6Oe0o1yIfdm2+2F3yHLjuaktukGVEOjju2txQXu8c= -github.com/grafana/authlib/claims v0.0.0-20240814072707-6cffd53bb828/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso= +github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw= github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s= github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ= @@ -2347,16 +2283,18 @@ github.com/grafana/grafana-plugin-sdk-go v0.244.0 h1:ZZxHbiiF6QcsnlbPFyZGmzNDoTC github.com/grafana/grafana-plugin-sdk-go v0.244.0/go.mod h1:H3FXrJMUlwocQ6UYj8Ds5I9EzRAVOcdRcgaRE3mXQqk= github.com/grafana/grafana/pkg/aggregator v0.0.0-20240813192817-1b0e6b5c09b2 h1:2H9x4q53pkfUGtSNYD1qSBpNnxrFgylof/TYADb5xMI= github.com/grafana/grafana/pkg/aggregator v0.0.0-20240813192817-1b0e6b5c09b2/go.mod h1:gBLBniiSUQvyt4LRrpIeysj8Many0DV+hdUKifRE0Ec= -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/apiserver v0.0.0-20240708134731-e9876749d440 h1:833vWSgndCcOXycwCq2Y98W8+W2ouuuhTL+Gf3BNKg8= -github.com/grafana/grafana/pkg/apiserver v0.0.0-20240708134731-e9876749d440/go.mod h1:qfZc7FEYBdKcxHUTtWtEAH+ArbMIkEQnbVPzr8giY3k= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da h1:2E3c/I3ayAy4Z1GwIPqXNZcpUccRapE1aBXA1ho4g7o= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240821155123-6891eb1d35da/go.mod h1:p09fvU5ujNL/Ig8HB7g4f+S0zyYbQq3x/f0jA4ujVOM= +github.com/grafana/grafana/pkg/apiserver v0.0.0-20240821155123-6891eb1d35da h1:xQMb8cRZYu7D0IO9q/lB7qFQpLGAoPUnCase1CGHrXY= +github.com/grafana/grafana/pkg/apiserver v0.0.0-20240821155123-6891eb1d35da/go.mod h1:8kZIdcgyLiHBwXbZFFzg9XxM9zD8Ie3wkDhxWuqa5Oo= github.com/grafana/grafana/pkg/promlib v0.0.6 h1:FuRyHMIgVVXkLuJnCflNfk3gqJflmyiI+/ZuJ9MoAfY= github.com/grafana/grafana/pkg/promlib v0.0.6/go.mod h1:shFkrG1fQ/PPNRGhxAPNMLp0SAeG/jhqaLoG6n2191M= github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435 h1:SNEeqY22DrGr5E9kGF1mKSqlOom14W9+b1u4XEGJowA= github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435/go.mod h1:8cz+z0i57IjN6MYmu/zZQdCg9CQcsnEHbaJBBEf3KQo= -github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240624122844-a89deaeb7365 h1:XRHqYGxjN2+/4QHPoOtr7kYTL9p2P5UxTXfnbiaO/NI= -github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240624122844-a89deaeb7365/go.mod h1:X4dwV2eQI8z8G2aHXvhZZXu/y/rb3psQXuaZa66WZfA= +github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20240821183201-2f012860344d h1:3oeqPfkTy3hJproHFj6NHx0mJDMU8bpU7ERcKF+C+dA= +github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20240821183201-2f012860344d/go.mod h1:M55oqs8MKOMCUkRCcPf9+a9r1kjiH8Dx5SR91EbfOgA= +github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240821161612-71f0dae39e9d h1:cmJmy/KdlD+8EOWn9AogfRMr9tWoWPDnZ180sQxD/IA= +github.com/grafana/grafana/pkg/storage/unified/resource v0.0.0-20240821161612-71f0dae39e9d/go.mod h1:KL0LyEIlmuRi/zzuCopFZSmSJzBVu2hMGHIK74i4iE8= github.com/grafana/grafana/pkg/util/xorm v0.0.1 h1:72QZjxWIWpSeOF8ob4aMV058kfgZyeetkAB8dmeti2o= github.com/grafana/grafana/pkg/util/xorm v0.0.1/go.mod h1:eNfbB9f2jM8o9RfwqwjY8SYm5tvowJ8Ly+iE4P9rXII= github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= @@ -2400,8 +2338,6 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= github.com/hairyhenderson/go-which v0.2.0 h1:vxoCKdgYc6+MTBzkJYhWegksHjjxuXPNiqo5G2oBM+4= github.com/hairyhenderson/go-which v0.2.0/go.mod h1:U1BQQRCjxYHfOkXDyCgst7OZVknbqI7KuGKhGnmyIik= -github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= -github.com/hanwen/go-fuse/v2 v2.1.0/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc= github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= @@ -2536,51 +2472,14 @@ github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/ionos-cloud/sdk-go/v6 v6.1.11 h1:J/uRN4UWO3wCyGOeDdMKv8LWRzKu6UIkLEaes38Kzh8= github.com/ionos-cloud/sdk-go/v6 v6.1.11/go.mod h1:EzEgRIDxBELvfoa/uBN0kOQaqovLjUWEB7iW4/Q+t4k= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= @@ -2610,7 +2509,6 @@ github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuT github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jmattheis/goverter v1.4.0/go.mod h1:iVIl/4qItWjWj2g3vjouGoYensJbRqDHpzlEVMHHFeY= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -2618,7 +2516,6 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= @@ -2657,10 +2554,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= github.com/klauspost/asmfmt v1.3.2/go.mod h1:AG8TuvYojzulgDAMCnYn50l/5QV3Bs/tp6j0HLHbNSE= -github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= @@ -2685,12 +2580,10 @@ github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NB github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= @@ -2702,10 +2595,7 @@ github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353 h1:X/79QL0b4YJVO5+O github.com/leesper/go_rng v0.0.0-20190531154944-a612b043e353/go.mod h1:N0SVk0uhy+E1PZ3C9ctsPRlvOPAFPkCNlcPBDkt0N3U= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= @@ -2741,7 +2631,6 @@ github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxq github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -2750,13 +2639,10 @@ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= -github.com/mattn/go-ieproxy v0.0.3/go.mod h1:6ZpRmhBaYuBX1U2za+9rC9iCGLsSp2tftelZne7CPko= github.com/mattn/go-ieproxy v0.0.11 h1:MQ/5BuGSgDAHZOJe6YY80IF2UVCfGkwfo6AeD7HtHYo= github.com/mattn/go-ieproxy v0.0.11/go.mod h1:/NsJd+kxZBmjMc5hrJCKMbP57B84rvq9BiDRbtO9AS0= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= @@ -2825,7 +2711,6 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -3033,7 +2918,6 @@ github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= @@ -3156,11 +3040,8 @@ github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys= github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -3270,7 +3151,6 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= @@ -3321,10 +3201,7 @@ github.com/uber/jaeger-client-go v2.30.0+incompatible h1:D6wyKGCecFaSRUpo8lCVbaO github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVKhn2Um6rjCsSsg= github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI= @@ -3391,7 +3268,6 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= go.einride.tech/aip v0.66.0/go.mod h1:qAhMsfT7plxBX+Oy7Huol6YUvZ0ZzdUz26yZsQwfl1M= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= @@ -3426,7 +3302,6 @@ go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwD go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc= go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= -go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= @@ -3512,7 +3387,6 @@ go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= @@ -3520,7 +3394,6 @@ go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0 go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= @@ -3528,30 +3401,25 @@ go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= -go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -gocloud.dev v0.25.0 h1:Y7vDq8xj7SyM848KXf32Krda2e6jQ4CLh/mTeCSqXtk= -gocloud.dev v0.25.0/go.mod h1:7HegHVCYZrMiU3IE1qtnzf/vRrDwLYnRNR3EhWX8x9Y= +gocloud.dev v0.39.0 h1:EYABYGhAalPUaMrbSKOr5lejxoxvXj99nE8XFtsDgds= +gocloud.dev v0.39.0/go.mod h1:drz+VyYNBvrMTW0KZiBAYEdl8lbNZx+OQ7oQvdrFmSQ= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -3565,17 +3433,13 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220511200225-c6db032c6c88/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -3740,13 +3604,10 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= @@ -3795,7 +3656,6 @@ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210427180440-81ed05c6b58c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= @@ -3868,7 +3728,6 @@ golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -3910,7 +3769,6 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -3932,7 +3790,6 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210503080704-8803ae5d1324/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -3952,20 +3809,16 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -4004,7 +3857,6 @@ golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808/go.mod h1:KG1lNk5ZFNssSZLrpVb4sMXKMpGwGXOxSG3rnu2gZQQ= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -4057,13 +3909,12 @@ golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20220922220347-f3bd1da661af/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -4075,9 +3926,7 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -4086,7 +3935,6 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -4162,8 +4010,6 @@ golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -4172,8 +4018,9 @@ golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= +golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= gonum.org/v1/gonum v0.7.0/go.mod h1:L02bwd0sqlsvRv41G7wGWFCsVNZFv/k1xzGIxeANHGM= gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= @@ -4209,7 +4056,6 @@ google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34q google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.46.0/go.mod h1:ceL4oozhkAiTID8XMmJBsIxID/9wMXJVVFXPg4ylg3I= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= @@ -4218,15 +4064,10 @@ google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6 google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM= -google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.68.0/go.mod h1:sOM8pTpwgflXRhz+oC8H2Dr+UcbMqkPPWNJo88Q7TH8= -google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7C80= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= @@ -4269,8 +4110,8 @@ google.golang.org/api v0.162.0/go.mod h1:6SulDkfoBIg4NFmCuZ39XeeAgSHCPecfSUuDyYl google.golang.org/api v0.164.0/go.mod h1:2OatzO7ZDQsoS7IFf3rvsE17/TldiU3F/zxFHeqUB5o= google.golang.org/api v0.169.0/go.mod h1:gpNOiMA2tZ4mf5R9Iwf4rK/Dcz0fbdIgWYWVoxmsyLg= google.golang.org/api v0.174.0/go.mod h1:aC7tB6j0HR1Nl0ni5ghpx6iLasmAX78Zkh/wgxAAjLg= -google.golang.org/api v0.176.0 h1:dHj1/yv5Dm/eQTXiP9hNCRT3xzJHWXeNdRq29XbMxoE= -google.golang.org/api v0.176.0/go.mod h1:Rra+ltKu14pps/4xTycZfobMgLpbosoaaL7c+SEMrO8= +google.golang.org/api v0.191.0 h1:cJcF09Z+4HAB2t5qTQM1ZtfL/PemsLFkcFG67qq2afk= +google.golang.org/api v0.191.0/go.mod h1:tD5dsFGxFza0hnQveGfVk9QQYKcfp+VzgRqyXFxE0+E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -4331,9 +4172,7 @@ google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210429181445-86c259c2b4ab/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= @@ -4349,31 +4188,21 @@ google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220204002441-d6cc3cc0770e/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220216160803-4663080d8bc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= @@ -4464,8 +4293,9 @@ google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80/go.mod h1:cc8bqMqt google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= google.golang.org/genproto v0.0.0-20240205150955-31a09d347014/go.mod h1:xEgQu1e4stdSSsxPDK8Azkrk/ECl5HvdPf6nbZrTS5M= google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= +google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= google.golang.org/genproto/googleapis/api v0.0.0-20230525234020-1aefcd67740a/go.mod h1:ts19tUU+Z0ZShN1y3aPyq2+O3d5FUNNgT6FtOzmrNn8= google.golang.org/genproto/googleapis/api v0.0.0-20230525234035-dd9d682886f9/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= @@ -4496,8 +4326,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go. google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 h1:0+ozOGcrp+Y8Aq8TLNN2Aliibms5LEzsq99ZZmAGYm0= -google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094/go.mod h1:fJ/e3If/Q67Mj99hin0hMhiNyCRmt6BQ2aWIJshUSJw= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988 h1:+/tmTy5zAieooKIXfzDm9KiA3Bv6JBwriRN9LY+yayk= +google.golang.org/genproto/googleapis/api v0.0.0-20240812133136-8ffd90a71988/go.mod h1:4+X6GvPs+25wZKbQq9qyAXrwIRExv7w0Ea6MgZLZiDM= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= @@ -4542,8 +4372,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415141817-7cd4c1c1f9ec/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988 h1:V71AcdLZr2p8dC9dbOIMCpqi4EmRl8wUwnJzXXLmbmc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240812133136-8ffd90a71988/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -4653,7 +4483,6 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo= gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE= gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= @@ -4807,7 +4636,6 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8= modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= -nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= diff --git a/go.work.sum b/go.work.sum index b6dfbca4091..33ce7370266 100644 --- a/go.work.sum +++ b/go.work.sum @@ -197,8 +197,6 @@ cloud.google.com/go/iot v1.7.5 h1:munTeBlbqI33iuTYgXy7S8lW2TCgi5l1hA4roSIY+EE= cloud.google.com/go/iot v1.7.11 h1:UBqSUZA6+7bM+mv6uvhl8tVsyT2Fi50njtBFRbrKSlI= cloud.google.com/go/iot v1.7.11/go.mod h1:0vZJOqFy9kVLbUXwTP95e0dWHakfR4u5IWqsKMGIfHk= cloud.google.com/go/kms v1.18.4/go.mod h1:SG1bgQ3UWW6/KdPo9uuJnzELXY5YTTMJtDYvajiQ22g= -cloud.google.com/go/kms v1.18.5 h1:75LSlVs60hyHK3ubs2OHd4sE63OAMcM2BdSJc2bkuM4= -cloud.google.com/go/kms v1.18.5/go.mod h1:yXunGUGzabH8rjUPImp2ndHiGolHeWJJ0LODLedicIY= cloud.google.com/go/language v1.12.3 h1:iaJZg6K4j/2PvZZVcjeO/btcWWIllVRBhuTFjGO4LXs= cloud.google.com/go/language v1.13.0 h1:6Pl97Ei85A3wBJwjXW2S/1IWeUvhQf/lIPQBItnp0FA= cloud.google.com/go/language v1.13.0/go.mod h1:B9FbD17g1EkilctNGUDAdSrBHiFOlKNErLljO7jplDU= @@ -387,6 +385,7 @@ contrib.go.opencensus.io/exporter/stackdriver v0.13.10 h1:a9+GZPUe+ONKUwULjlEOuc contrib.go.opencensus.io/exporter/stackdriver v0.13.14 h1:zBakwHardp9Jcb8sQHcHpXy/0+JIb1M8KjigCJzx7+4= contrib.go.opencensus.io/exporter/stackdriver v0.13.14/go.mod h1:5pSSGY0Bhuk7waTHuDf4aQ8D2DrhgETRo9fy6k3Xlzc= contrib.go.opencensus.io/integrations/ocsql v0.1.7 h1:G3k7C0/W44zcqkpRSFyjU9f6HZkbwIrL//qqnlqWZ60= +contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUuKccphEpTJnWhS2jkQyMt6Y7pJCD7fY= docker.io/go-docker v1.0.0 h1:VdXS/aNYQxyA9wdLD5z8Q8Ro688/hG8HzKxYVEVbE6s= docker.io/go-docker v1.0.0/go.mod h1:7tiAn5a0LFmjbPDbyTPOaTTOuG1ZRNXdPA6RvKY+fpY= @@ -399,16 +398,10 @@ github.com/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84J github.com/Azure/azure-amqp-common-go/v3 v3.2.3/go.mod h1:7rPmbSfszeovxGfc5fSAXE4ehlXQZHpMja2OtxC2Tas= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.12.0/go.mod h1:99EvauvlcJ1U06amZiksfYz/3aFGyIhWGHVyiZXtBAI= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.1.0/go.mod h1:bhXu1AjYL+wutSL/kpSq6s7733q2Rb0yuot9Zgfqa/0= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo= github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI= github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0 h1:m/sWOGCREuSBqg2htVQTBY8nOZpyajYztF0vUvSZTuM= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.10.0/go.mod h1:Pu5Zksi2KrU7LPbZbNINx6fuVrUp/ffvpxdDj+i8LeE= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1 h1:FbH3BbSb4bvGluTesZZ+ttN/MDsnMmQP36OSnDuSXqw= -github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.7.1/go.mod h1:9V2j0jn9jDEkCkv8w/bKTNppX/d0FVA1ud77xCIP4KA= github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1 h1:o/Ws6bEqMeKZUfj1RRm3mQ51O8JGU5w+Qdg2AhHib6A= github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.7.1/go.mod h1:6QAMYBAbQeeKX+REFJMZ1nFWu9XLw/PPcjYpuc9RDFs= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= @@ -573,6 +566,7 @@ github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf h1:CAKfRE2YtTUIjjh1bkBtyYFaUT/WmOqsJjgtihT0vMI= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= @@ -601,11 +595,13 @@ github.com/dave/patsy v0.0.0-20210517141501-957256f50cba h1:1o36L4EKbZzazMk8iGC4 github.com/dave/rebecca v0.9.1 h1:jxVfdOxRirbXL28vXMvUvJ1in3djwkVKXCq339qhBL0= github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= +github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 h1:tkum0XDgfR0jcVVXuTsYv/erY2NnEDqwRojbxR1rBYA= github.com/denisenkom/go-mssqldb v0.12.0 h1:VtrkII767ttSPNRfFekePK3sctr+joXgO58stqQbtUA= github.com/devigned/tab v0.1.1 h1:3mD6Kb1mUOYeLpJvTVSDwSg5ZsfSxfvxGRTxRsJsITA= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dhui/dktest v0.3.0 h1:kwX5a7EkLcjo7VpsPQSYJcKGbXBXdjI9FGjuUj1jn6I= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/drone/drone-runtime v1.1.0 h1:IsKbwiLY6+ViNBzX0F8PERJVZZcEJm9rgxEh3uZP5IE= @@ -643,6 +639,7 @@ github.com/fsouza/fake-gcs-server v1.7.0 h1:Un0BXUXrRWYSmYyC1Rqm2e2WJfTPyDy/HGMz github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= @@ -717,8 +714,6 @@ github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grafana/authlib v0.0.0-20240730122259-a0d13672efb1/go.mod h1:YA9We4kTafu7mlMnUh3In6Q2wpg8fYN3ycgCKOK1TB8= github.com/grafana/authlib/claims v0.0.0-20240809101159-74eaccc31a06/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= -github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db h1:mDk0bwRV6rDrLSmKXftcPf9kLA9uH6EvxJvzpPW9bso= -github.com/grafana/authlib/claims v0.0.0-20240814074258-eae7d47f01db/go.mod h1:r+F8H6awwjNQt/KPZ2GNwjk8TvsJ7/gxzkXN26GlL/A= github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU= github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= @@ -837,6 +832,7 @@ github.com/knadh/koanf/v2 v2.1.0/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkX github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= +github.com/kr/pty v1.1.1 h1:VkoXIwSboBpnk99O/KFauAEILuNHv5DVFKZMBN/gUgw= github.com/kr/pty v1.1.8 h1:AkaSdXYQOWeaO3neb8EM634ahkXXe3jYbVh/F9lq+GI= github.com/kshvakov/clickhouse v1.3.5 h1:PDTYk9VYgbjPAWry3AoDREeMgOVUFij6bh6IjlloHL0= github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= @@ -1252,6 +1248,7 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240513163218-0867130af1f8/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240624140628-dc46fd24d27d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240711142825-46eb208f015d/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= From e4953b6ffd37f0f8201e62dd5668a08de895e9f8 Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:25:25 -0400 Subject: [PATCH 211/229] Chore: Add Dockerfile CI check for new modules (#92239) --- .github/workflows/pr-go-workspace-check.yml | 4 +- scripts/go-workspace/main.go | 41 ++++++++++++++++++++- scripts/go-workspace/validate-dockerfile.sh | 8 ++++ 3 files changed, 51 insertions(+), 2 deletions(-) create mode 100755 scripts/go-workspace/validate-dockerfile.sh diff --git a/.github/workflows/pr-go-workspace-check.yml b/.github/workflows/pr-go-workspace-check.yml index 699f211e1e9..bc5385ef0e0 100644 --- a/.github/workflows/pr-go-workspace-check.yml +++ b/.github/workflows/pr-go-workspace-check.yml @@ -30,4 +30,6 @@ jobs: echo "Please run 'make update-workspace' and commit the changes." echo "If there is a change in enterprise dependencies, please update pkg/extensions/main.go." exit 1 - fi \ No newline at end of file + fi + - name: Ensure Dockerfile contains submodule COPY commands + run: ./scripts/go-workspace/validate-dockerfile.sh \ No newline at end of file diff --git a/scripts/go-workspace/main.go b/scripts/go-workspace/main.go index d25ab44214f..149b117952d 100644 --- a/scripts/go-workspace/main.go +++ b/scripts/go-workspace/main.go @@ -19,6 +19,8 @@ func main() { switch os.Args[1] { case "list-submodules": err = listSubmodules() + case "validate-dockerfile": + err = validateDockerfile() default: printUsage() } @@ -40,7 +42,9 @@ func listSubmodules() error { delimiter := fs.String("delimiter", "\n", "Delimiter to use when printing paths") skip := fs.String("skip", "", "Skip submodules with this comment tag") help := fs.Bool("help", false, "Print help message") - fs.Parse(os.Args[2:]) + if err := fs.Parse(os.Args[2:]); err != nil { + return err + } if *help { fs.Usage() @@ -60,6 +64,41 @@ func listSubmodules() error { return nil } +func validateDockerfile() error { + fs := flag.NewFlagSet("validate-dockerfile", flag.ExitOnError) + workPath := fs.String("path", "go.work", "Path to go.work") + dockerfilePath := fs.String("dockerfile-path", "Dockerfile", "Path to Dockerfile") + skip := fs.String("skip", "", "Skip submodules with this comment tag") + if err := fs.Parse(os.Args[2:]); err != nil { + return err + } + + dockerFileRaw, err := os.ReadFile(*dockerfilePath) + if err != nil { + return err + } + dockerFile := string(dockerFileRaw) + + workfile, err := parseGoWork(*workPath) + if err != nil { + return err + } + + paths := getSubmodulePaths(workfile, *skip) + for _, p := range paths { + path := strings.TrimPrefix(p, "./") + if path == "" || path == "." { + continue + } + if !strings.Contains(dockerFile, path) { + return fmt.Errorf("the Dockerfile is missing `COPY %s/go.* %s` for the related module. Please add it and commit the change.", path, path) + } + } + + fmt.Println("All submodules are included in the Dockerfile.") + return nil +} + func getSubmodulePaths(wf *modfile.WorkFile, skip string) []string { var paths []string for _, d := range wf.Use { diff --git a/scripts/go-workspace/validate-dockerfile.sh b/scripts/go-workspace/validate-dockerfile.sh new file mode 100755 index 00000000000..17ee08b6a51 --- /dev/null +++ b/scripts/go-workspace/validate-dockerfile.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/../.. +go run scripts/go-workspace/main.go validate-dockerfile --path "${REPO_ROOT}/go.work" --dockerfile-path "${REPO_ROOT}/Dockerfile" \ No newline at end of file From 733ae1f0996b8475456dab363a07089127edd58c Mon Sep 17 00:00:00 2001 From: Todd Treece <360020+toddtreece@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:47:58 -0400 Subject: [PATCH 212/229] Chore: Skip gzip for apiserver routes (#92245) --- pkg/middleware/gziper.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/middleware/gziper.go b/pkg/middleware/gziper.go index 02069695bf6..0d9a0a97c65 100644 --- a/pkg/middleware/gziper.go +++ b/pkg/middleware/gziper.go @@ -42,6 +42,7 @@ func prefix(p string) matcher { return func(s string) bool { return strings.HasP func substr(p string) matcher { return func(s string) bool { return strings.Contains(s, p) } } var gzipIgnoredPaths = []matcher{ + prefix("/apis"), // apiserver handles its own compression https://github.com/kubernetes/kubernetes/blob/b60e01f881aa8a74b44d0ac1000e4f67f854273b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/responsewriters/writers.go#L155-L158 prefix("/api/datasources"), prefix("/api/plugins"), prefix("/api/plugin-proxy/"), From 2ad9d8cafe33a43021e148b1aa7a7cebba61b8ff Mon Sep 17 00:00:00 2001 From: Lucy Chen <140550297+lucychen-grafana@users.noreply.github.com> Date: Wed, 21 Aug 2024 18:36:20 -0400 Subject: [PATCH 213/229] ShareModal: delete shareView query param from url (#92243) --- .../dashboard-scene/utils/urlBuilders.test.ts | 11 ++++++++++- .../app/features/dashboard-scene/utils/urlBuilders.ts | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/utils/urlBuilders.test.ts b/public/app/features/dashboard-scene/utils/urlBuilders.test.ts index 16a683cace6..4e7f02369f0 100644 --- a/public/app/features/dashboard-scene/utils/urlBuilders.test.ts +++ b/public/app/features/dashboard-scene/utils/urlBuilders.test.ts @@ -28,7 +28,16 @@ describe('dashboard utils', () => { expect(url).toBe('/d/dash-1/dash-1-slug/panel-edit/2?orgId=1&filter=A'); }); - it('Can getUrl with params removed and addded', () => { + it('Can getURL without shareView param', async () => { + const url = getDashboardUrl({ + uid: 'dash-1', + currentQueryParams: '?orgId=1&filter=A&shareView=link', + }); + + expect(url).toBe('/d/dash-1?orgId=1&filter=A'); + }); + + it('Can getUrl with params removed and added', () => { const url = getDashboardUrl({ uid: 'dash-1', currentQueryParams: '?orgId=1&filter=A', diff --git a/public/app/features/dashboard-scene/utils/urlBuilders.ts b/public/app/features/dashboard-scene/utils/urlBuilders.ts index 4c059d3a6e6..4d4104f100d 100644 --- a/public/app/features/dashboard-scene/utils/urlBuilders.ts +++ b/public/app/features/dashboard-scene/utils/urlBuilders.ts @@ -62,6 +62,8 @@ export function getDashboardUrl(options: DashboardUrlOptions) { const params = options.currentQueryParams ? locationSearchToObject(options.currentQueryParams) : {}; + delete params['shareView']; + if (options.updateQuery) { for (const key in options.updateQuery) { // removing params with null | undefined From 130a86d9c79551e339b0f5c715ab140b10800187 Mon Sep 17 00:00:00 2001 From: Brendan O'Handley Date: Wed, 21 Aug 2024 20:53:44 -0500 Subject: [PATCH 214/229] Explore metrics: Improve performance for build layout (#92238) * revert buildLayout * filter metric names using metricPrefix using regex * build groups with all the metric names and only build them once * remove commented code * use the metrics search input results to build the prefix select options * simplify prefix regex because we do not have to do it at the same time as the metrics search input regex --- .../trails/MetricSelect/MetricSelectScene.tsx | 95 +++++++++---------- 1 file changed, 45 insertions(+), 50 deletions(-) diff --git a/public/app/features/trails/MetricSelect/MetricSelectScene.tsx b/public/app/features/trails/MetricSelect/MetricSelectScene.tsx index d2bc0e63e54..b629f1f9b7e 100644 --- a/public/app/features/trails/MetricSelect/MetricSelectScene.tsx +++ b/public/app/features/trails/MetricSelect/MetricSelectScene.tsx @@ -29,7 +29,6 @@ import { import { Alert, Field, Icon, IconButton, InlineSwitch, Input, Select, Tooltip, useStyles2 } from '@grafana/ui'; import { Trans } from 'app/core/internationalization'; -import { DataTrail } from '../DataTrail'; import { MetricScene } from '../MetricScene'; import { StatusWrapper } from '../StatusWrapper'; import { Node, Parser } from '../groop/parser'; @@ -214,10 +213,20 @@ export class MetricSelectScene extends SceneObjectBase i try { const response = await getMetricNames(datasourceUid, timeRange, match, MAX_METRIC_NAMES); const searchRegex = createJSRegExpFromSearchTerms(getMetricSearch(this)); - const metricNames = searchRegex + let metricNames = searchRegex ? response.data.filter((metric) => !searchRegex || searchRegex.test(metric)) : response.data; + // use this to generate groups for metric prefix + const filteredMetricNames = metricNames; + + // filter the remaining metrics with the metric prefix + const metricPrefix = this.state.metricPrefix; + if (metricPrefix && metricPrefix !== 'all') { + const prefixRegex = new RegExp(`(^${metricPrefix}.*)`, 'igy'); + metricNames = metricNames.filter((metric) => !prefixRegex || prefixRegex.test(metric)); + } + const metricNamesWarning = response.limitReached ? `This feature will only return up to ${MAX_METRIC_NAMES} metric names for performance reasons. ` + `This limit is being exceeded for the current data source. ` + @@ -225,7 +234,11 @@ export class MetricSelectScene extends SceneObjectBase i : undefined; let bodyLayout = this.state.body; - const rootGroupNode = await this.generateGroups(metricNames); + + let rootGroupNode = this.state.rootGroup; + + // generate groups based on the search metrics input + rootGroupNode = await this.generateGroups(filteredMetricNames); this.setState({ metricNames, @@ -311,6 +324,21 @@ export class MetricSelectScene extends SceneObjectBase i this.buildLayout(); } + private sortedPreviewMetrics() { + return Object.values(this.previewCache).sort((a, b) => { + if (a.isEmpty && b.isEmpty) { + return a.index - b.index; + } + if (a.isEmpty) { + return 1; + } + if (b.isEmpty) { + return -1; + } + return a.index - b.index; + }); + } + private async buildLayout() { // Temp hack when going back to select metric scene and variable updates if (this.ignoreNextUpdate) { @@ -318,67 +346,32 @@ export class MetricSelectScene extends SceneObjectBase i return; } - if (!this.state.rootGroup) { - const rootGroupNode = await this.generateGroups(this.state.metricNames); - this.setState({ rootGroup: rootGroupNode }); - } - - const children = await this.populateFilterableViewLayout(); - const rowTemplate = this.state.showPreviews ? ROW_PREVIEW_HEIGHT : ROW_CARD_HEIGHT; - this.state.body.setState({ children, autoRows: rowTemplate }); - } + const children: SceneFlexItem[] = []; - private async populateFilterableViewLayout() { const trail = getTrailFor(this); + + const metricsList = this.sortedPreviewMetrics(); + // Get the current filters to determine the count of them // Which is required for `getPreviewPanelFor` const filters = getFilters(this); - - let rootGroupNode = this.state.rootGroup; - if (!rootGroupNode) { - rootGroupNode = await this.generateGroups(this.state.metricNames); - this.setState({ rootGroup: rootGroupNode }); - } - - const children: SceneFlexItem[] = []; - - for (const [groupKey, groupNode] of rootGroupNode.groups) { - if (this.state.metricPrefix !== METRIC_PREFIX_ALL && this.state.metricPrefix !== groupKey) { - continue; - } - - for (const [_, value] of groupNode.groups) { - const panels = await this.populatePanels(trail, filters, value.values); - children.push(...panels); - } - - const morePanelsMaybe = await this.populatePanels(trail, filters, groupNode.values); - children.push(...morePanelsMaybe); - } - - return children; - } - - private async populatePanels(trail: DataTrail, filters: ReturnType, values: string[]) { const currentFilterCount = filters?.length || 0; - const previewPanelLayoutItems: SceneFlexItem[] = []; - for (let index = 0; index < values.length; index++) { - const metricName = values[index]; - const metric: MetricPanel = this.previewCache[metricName] ?? { name: metricName, index, loaded: false }; - const metadata = await trail.getMetricMetadata(metricName); + for (let index = 0; index < metricsList.length; index++) { + const metric = metricsList[index]; + const metadata = await trail.getMetricMetadata(metric.name); const description = getMetricDescription(metadata); if (this.state.showPreviews) { if (metric.itemRef && metric.isPanel) { - previewPanelLayoutItems.push(metric.itemRef.resolve()); + children.push(metric.itemRef.resolve()); continue; } const panel = getPreviewPanelFor(metric.name, index, currentFilterCount, description); metric.itemRef = panel.getRef(); metric.isPanel = true; - previewPanelLayoutItems.push(panel); + children.push(panel); } else { const panel = new SceneCSSGridItem({ $variables: new SceneVariableSet({ @@ -388,11 +381,13 @@ export class MetricSelectScene extends SceneObjectBase i }); metric.itemRef = panel.getRef(); metric.isPanel = false; - previewPanelLayoutItems.push(panel); + children.push(panel); } } - return previewPanelLayoutItems; + const rowTemplate = this.state.showPreviews ? ROW_PREVIEW_HEIGHT : ROW_CARD_HEIGHT; + + this.state.body.setState({ children, autoRows: rowTemplate }); } public updateMetricPanel = (metric: string, isLoaded?: boolean, isEmpty?: boolean) => { @@ -416,7 +411,7 @@ export class MetricSelectScene extends SceneObjectBase i public onPrefixFilterChange = (val: SelectableValue) => { this.setState({ metricPrefix: val.value }); - this.buildLayout(); + this._refreshMetricNames(); }; public reportPrefixFilterInteraction = (isMenuOpen: boolean) => { From db711d6a21201cc66f70babafc28f125f40e1578 Mon Sep 17 00:00:00 2001 From: Konrad Lalik Date: Thu, 22 Aug 2024 08:16:28 +0200 Subject: [PATCH 215/229] Alerting: Allow groups and namespaces with slashes (#92183) * Allow rule groups and namespaces with slashes * Fix lint --- .../app/features/alerting/unified/api/ruler.ts | 10 +++++++--- .../components/rule-editor/FolderAndGroup.tsx | 7 ------- .../rule-editor/GroupAndNamespaceFields.tsx | 8 -------- .../components/rule-editor/util.test.ts | 18 ++++++++---------- .../unified/components/rule-editor/util.ts | 14 ++------------ .../rules/EditRuleGroupModal.test.tsx | 10 +--------- .../components/rules/EditRuleGroupModal.tsx | 9 --------- 7 files changed, 18 insertions(+), 58 deletions(-) diff --git a/public/app/features/alerting/unified/api/ruler.ts b/public/app/features/alerting/unified/api/ruler.ts index 331d45154d0..df265d53bad 100644 --- a/public/app/features/alerting/unified/api/ruler.ts +++ b/public/app/features/alerting/unified/api/ruler.ts @@ -5,7 +5,7 @@ import { FetchResponse, getBackendSrv } from '@grafana/runtime'; import { RulerDataSourceConfig } from 'app/types/unified-alerting'; import { PostableRulerRuleGroupDTO, RulerRuleGroupDTO, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; -import { checkForPathSeparator } from '../components/rule-editor/util'; +import { containsPathSeparator } from '../components/rule-editor/util'; import { RULER_NOT_SUPPORTED_MSG } from '../utils/constants'; import { getDatasourceAPIUid, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource'; @@ -73,11 +73,15 @@ interface RulerQueryDetailsProvider { group: (group: string) => GroupUrlParams; } +// some gateways (like Istio) will decode "/" and "\" characters – this will cause 404 errors for any API call +// that includes these values in the URL (ie. /my/path%2fto/resource -> /my/path/to/resource) +// +// see https://istio.io/latest/docs/ops/best-practices/security/#customize-your-system-on-path-normalization function getQueryDetailsProvider(rulerConfig: RulerDataSourceConfig): RulerQueryDetailsProvider { const isGrafanaDatasource = rulerConfig.dataSourceName === GRAFANA_RULES_SOURCE_NAME; const groupParamRewrite = (group: string): GroupUrlParams => { - if (checkForPathSeparator(group) !== true) { + if (containsPathSeparator(group) === true) { return { group: QUERY_GROUP_TAG, searchParams: { group } }; } return { group, searchParams: {} }; @@ -93,7 +97,7 @@ function getQueryDetailsProvider(rulerConfig: RulerDataSourceConfig): RulerQuery return { namespace: (namespace: string): NamespaceUrlParams => { - if (checkForPathSeparator(namespace) !== true) { + if (containsPathSeparator(namespace) === true) { return { namespace: QUERY_NAMESPACE_TAG, searchParams: { namespace } }; } return { namespace, searchParams: {} }; diff --git a/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx b/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx index a10eb9f7f19..c8e84b38183 100644 --- a/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx @@ -23,7 +23,6 @@ import { evaluateEveryValidationOptions } from '../rules/EditRuleGroupModal'; import { EvaluationGroupQuickPick } from './EvaluationGroupQuickPick'; import { containsSlashes, Folder, RuleFolderPicker } from './RuleFolderPicker'; -import { checkForPathSeparator } from './util'; export const MAX_GROUP_RESULTS = 1000; @@ -175,9 +174,6 @@ export function FolderAndGroup({ name="folder" rules={{ required: { value: true, message: 'Select a folder' }, - validate: { - pathSeparator: (folder: Folder) => checkForPathSeparator(folder.uid), - }, }} /> or @@ -251,9 +247,6 @@ export function FolderAndGroup({ control={control} rules={{ required: { value: true, message: 'Must enter a group name' }, - validate: { - pathSeparator: (group_: string) => checkForPathSeparator(group_), - }, }} /> diff --git a/public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx b/public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx index 541a7b6780a..eb3532f954a 100644 --- a/public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/GroupAndNamespaceFields.tsx @@ -10,8 +10,6 @@ import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelect import { fetchRulerRulesAction } from '../../state/actions'; import { RuleFormValues } from '../../types/rule-form'; -import { checkForPathSeparator } from './util'; - interface Props { rulesSourceName: string; } @@ -74,9 +72,6 @@ export const GroupAndNamespaceFields = ({ rulesSourceName }: Props) => { control={control} rules={{ required: { value: true, message: 'Required.' }, - validate: { - pathSeparator: checkForPathSeparator, - }, }} /> @@ -98,9 +93,6 @@ export const GroupAndNamespaceFields = ({ rulesSourceName }: Props) => { control={control} rules={{ required: { value: true, message: 'Required.' }, - validate: { - pathSeparator: checkForPathSeparator, - }, }} /> diff --git a/public/app/features/alerting/unified/components/rule-editor/util.test.ts b/public/app/features/alerting/unified/components/rule-editor/util.test.ts index ec51a60f31e..b5ffc31189c 100644 --- a/public/app/features/alerting/unified/components/rule-editor/util.test.ts +++ b/public/app/features/alerting/unified/components/rule-editor/util.test.ts @@ -3,7 +3,7 @@ import { ClassicCondition, ExpressionQuery } from 'app/features/expressions/type import { AlertQuery } from 'app/types/unified-alerting-dto'; import { - checkForPathSeparator, + containsPathSeparator, findRenamedDataQueryReferences, getThresholdsForQueries, queriesWithUpdatedReferences, @@ -229,17 +229,15 @@ describe('rule-editor', () => { }); }); -describe('checkForPathSeparator', () => { - it('should not allow strings with /', () => { - expect(checkForPathSeparator('foo / bar')).not.toBe(true); - expect(typeof checkForPathSeparator('foo / bar')).toBe('string'); +describe('containsPathSeparator', () => { + it('should return true for strings with /', () => { + expect(containsPathSeparator('foo / bar')).toBe(true); }); - it('should not allow strings with \\', () => { - expect(checkForPathSeparator('foo \\ bar')).not.toBe(true); - expect(typeof checkForPathSeparator('foo \\ bar')).toBe('string'); + it('should return true for strings with \\', () => { + expect(containsPathSeparator('foo \\ bar')).toBe(true); }); - it('should allow anything without / or \\', () => { - expect(checkForPathSeparator('foo bar')).toBe(true); + it('should return false for strings without / or \\', () => { + expect(containsPathSeparator('foo !@#$%^&*() <> [] {} bar')).toBe(false); }); }); diff --git a/public/app/features/alerting/unified/components/rule-editor/util.ts b/public/app/features/alerting/unified/components/rule-editor/util.ts index 7c09766a2db..716c9f2fbbc 100644 --- a/public/app/features/alerting/unified/components/rule-editor/util.ts +++ b/public/app/features/alerting/unified/components/rule-editor/util.ts @@ -1,5 +1,4 @@ import { xor } from 'lodash'; -import { ValidateResult } from 'react-hook-form'; import { DataFrame, @@ -89,17 +88,8 @@ export function refIdExists(queries: AlertQuery[], refId: string | null): boolea return queries.find((query) => query.refId === refId) !== undefined; } -// some gateways (like Istio) will decode "/" and "\" characters – this will cause 404 errors for any API call -// that includes these values in the URL (ie. /my/path%2fto/resource -> /my/path/to/resource) -// -// see https://istio.io/latest/docs/ops/best-practices/security/#customize-your-system-on-path-normalization -export function checkForPathSeparator(value: string): ValidateResult { - const containsPathSeparator = value.includes('/') || value.includes('\\'); - if (containsPathSeparator) { - return 'Cannot contain "/" or "\\" characters'; - } - - return true; +export function containsPathSeparator(value: string): boolean { + return value.includes('/') || value.includes('\\'); } // this function assumes we've already checked if the data passed in to the function is of the alert condition diff --git a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx index 7569cb33d18..65c65e07685 100644 --- a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx +++ b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.test.tsx @@ -1,4 +1,4 @@ -import { render, screen, userEvent } from 'test/test-utils'; +import { render } from 'test/test-utils'; import { byLabelText, byTestId, byText, byTitle } from 'testing-library-selector'; import { CombinedRuleNamespace } from 'app/types/unified-alerting'; @@ -158,12 +158,4 @@ describe('EditGroupModal component on grafana-managed alert rules', () => { expect(await ui.input.namespace.find()).toHaveValue('namespace1'); expect(ui.folderLink.query()).not.toBeInTheDocument(); }); - - it('does not allow slashes in the group name', async () => { - const user = userEvent.setup(); - renderWithGrafanaGroup(); - await user.type(await ui.input.group.find(), 'group/with/slashes'); - await user.click(ui.input.interval.get()); - expect(await screen.findByText(/cannot contain \"\/\"/i)).toBeInTheDocument(); - }); }); diff --git a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx index 883fd11c733..2ff9d57baaf 100644 --- a/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx +++ b/public/app/features/alerting/unified/components/rules/EditRuleGroupModal.tsx @@ -28,7 +28,6 @@ import { EvaluationIntervalLimitExceeded } from '../InvalidIntervalWarning'; import { decodeGrafanaNamespace, encodeGrafanaNamespace } from '../expressions/util'; import { EvaluationGroupQuickPick } from '../rule-editor/EvaluationGroupQuickPick'; import { MIN_TIME_RANGE_STEP_S } from '../rule-editor/GrafanaEvaluationBehavior'; -import { checkForPathSeparator } from '../rule-editor/util'; const ITEMS_PER_PAGE = 10; @@ -300,11 +299,6 @@ export function EditCloudGroupModal(props: ModalProps): React.ReactElement { readOnly={intervalEditOnly || isGrafanaManagedGroup} {...register('namespaceName', { required: 'Namespace name is required.', - validate: { - // for Grafana-managed we do not validate the name of the folder because we use the UID anyway - pathSeparator: (namespaceName) => - isGrafanaManagedGroup ? true : checkForPathSeparator(namespaceName), - }, })} /> @@ -337,9 +331,6 @@ export function EditCloudGroupModal(props: ModalProps): React.ReactElement { readOnly={intervalEditOnly} {...register('groupName', { required: 'Evaluation group name is required.', - validate: { - pathSeparator: (namespace) => checkForPathSeparator(namespace), - }, })} /> From 40cdfeb00b4e15ee3d65e70d9a99b7d603daf597 Mon Sep 17 00:00:00 2001 From: Sven Grossmann Date: Thu, 22 Aug 2024 09:12:50 +0200 Subject: [PATCH 216/229] Loki: Add `logsample` supporting query type (#92082) * Loki: Add `logsample` supporting query type * use `SupportingQueryType` * add missing test --- public/app/plugins/datasource/loki/datasource.test.ts | 3 +++ public/app/plugins/datasource/loki/datasource.ts | 1 + 2 files changed, 4 insertions(+) diff --git a/public/app/plugins/datasource/loki/datasource.test.ts b/public/app/plugins/datasource/loki/datasource.test.ts index 7fb44ddaf39..dfe34851ad9 100644 --- a/public/app/plugins/datasource/loki/datasource.test.ts +++ b/public/app/plugins/datasource/loki/datasource.test.ts @@ -1529,6 +1529,7 @@ describe('LokiDatasource', () => { queryType: 'range', refId: 'log-sample-A', maxLines: 20, + supportingQueryType: SupportingQueryType.LogsSample, }); }); @@ -1547,6 +1548,7 @@ describe('LokiDatasource', () => { queryType: LokiQueryType.Range, refId: 'log-sample-A', maxLines: 20, + supportingQueryType: SupportingQueryType.LogsSample, }); }); @@ -1564,6 +1566,7 @@ describe('LokiDatasource', () => { expr: '{label="value"}', queryType: LokiQueryType.Range, refId: 'log-sample-A', + supportingQueryType: SupportingQueryType.LogsSample, maxLines: 5, }); }); diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 80a95dda5a5..de497360aa1 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -242,6 +242,7 @@ export class LokiDatasource refId: `${REF_ID_STARTER_LOG_SAMPLE}${normalizedQuery.refId}`, expr: getLogQueryFromMetricsQuery(expr), maxLines: Number.isNaN(Number(options.limit)) ? this.maxLines : Number(options.limit), + supportingQueryType: SupportingQueryType.LogsSample, }; default: From 41ac5b5ae767b1cd1872e4fc4803f08197acbffb Mon Sep 17 00:00:00 2001 From: Ieva Date: Thu, 22 Aug 2024 10:04:06 +0100 Subject: [PATCH 217/229] RBAC: Fix an issue with server admins not being able to manage users in orgs that they don't belong to (#92024) * look at global perms if user is not a part of the target org * use constant * update tests --- .../accesscontrol/authorize_in_org_test.go | 98 +++++++------------ pkg/services/accesscontrol/middleware.go | 4 + 2 files changed, 41 insertions(+), 61 deletions(-) diff --git a/pkg/services/accesscontrol/authorize_in_org_test.go b/pkg/services/accesscontrol/authorize_in_org_test.go index 5c5819b1b26..8eb3082e1cc 100644 --- a/pkg/services/accesscontrol/authorize_in_org_test.go +++ b/pkg/services/accesscontrol/authorize_in_org_test.go @@ -1,7 +1,6 @@ package accesscontrol_test import ( - "context" "fmt" "net/http" "net/http/httptest" @@ -13,7 +12,6 @@ import ( "github.com/grafana/authlib/claims" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl" - "github.com/grafana/grafana/pkg/services/accesscontrol/actest" "github.com/grafana/grafana/pkg/services/authn" "github.com/grafana/grafana/pkg/services/authn/authntest" "github.com/grafana/grafana/pkg/services/authz/zanzana" @@ -22,7 +20,6 @@ import ( "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/team/teamtest" "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/services/user/usertest" "github.com/grafana/grafana/pkg/web" ) @@ -37,8 +34,8 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { orgIDGetter accesscontrol.OrgIDGetter evaluator accesscontrol.Evaluator accessControl accesscontrol.AccessControl - acService accesscontrol.Service - userCache user.Service + userIdentities []*authn.Identity + authnErrors []error ctxSignedInUser *user.SignedInUser teamService team.Service expectedStatus int @@ -48,7 +45,6 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { targetOrgId: accesscontrol.GlobalOrgID, evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac, - userCache: &usertest.FakeUserService{}, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}}, teamService: &teamtest.FakeService{}, @@ -60,7 +56,6 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac, - userCache: &usertest.FakeUserService{}, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, teamService: &teamtest.FakeService{}, expectedStatus: http.StatusOK, @@ -71,7 +66,6 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { targerOrgPermissions: []accesscontrol.Permission{}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac, - userCache: &usertest.FakeUserService{}, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{}}, teamService: &teamtest.FakeService{}, expectedStatus: http.StatusForbidden, @@ -82,7 +76,6 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac, - userCache: &usertest.FakeUserService{}, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, teamService: &teamtest.FakeService{}, expectedStatus: http.StatusOK, @@ -93,33 +86,10 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { targerOrgPermissions: []accesscontrol.Permission{}, evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac, - userCache: &usertest.FakeUserService{}, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, teamService: &teamtest.FakeService{}, expectedStatus: http.StatusForbidden, }, - { - name: "should return 403 when user org ID doesn't match and user does not exist in org 2", - targetOrgId: 2, - targerOrgPermissions: []accesscontrol.Permission{}, - evaluator: accesscontrol.EvalPermission("users:read", "users:*"), - accessControl: ac, - userCache: &usertest.FakeUserService{ExpectedError: fmt.Errorf("user not found")}, - ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, - teamService: &teamtest.FakeService{}, - expectedStatus: http.StatusForbidden, - }, - { - name: "should return 403 early when api key org ID doesn't match", - targetOrgId: 2, - targerOrgPermissions: []accesscontrol.Permission{}, - evaluator: accesscontrol.EvalPermission("users:read", "users:*"), - accessControl: ac, - userCache: &usertest.FakeUserService{}, - ctxSignedInUser: &user.SignedInUser{ApiKeyID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, - teamService: &teamtest.FakeService{}, - expectedStatus: http.StatusForbidden, - }, { name: "should fetch user permissions when org ID doesn't match", targetOrgId: 2, @@ -127,13 +97,8 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac, teamService: &teamtest.FakeService{}, - userCache: &usertest.FakeUserService{ - GetSignedInUserFn: func(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) { - return &user.SignedInUser{UserID: 1, OrgID: 2, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, nil - }, - }, - ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:write": {"users:*"}}}}, - expectedStatus: http.StatusOK, + ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:write": {"users:*"}}}}, + expectedStatus: http.StatusOK, }, { name: "fails to fetch user permissions when org ID doesn't match", @@ -142,16 +107,9 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac, teamService: &teamtest.FakeService{}, - acService: &actest.FakeService{ - ExpectedErr: fmt.Errorf("failed to get user permissions"), - }, - userCache: &usertest.FakeUserService{ - GetSignedInUserFn: func(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) { - return &user.SignedInUser{UserID: 1, OrgID: 2, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, nil - }, - }, - ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, - expectedStatus: http.StatusForbidden, + authnErrors: []error{fmt.Errorf("failed to get user permissions")}, + ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, + expectedStatus: http.StatusForbidden, }, { name: "unable to get target org", @@ -160,24 +118,35 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { }, evaluator: accesscontrol.EvalPermission("users:read", "users:*"), accessControl: ac, - userCache: &usertest.FakeUserService{}, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:read": {"users:*"}}}}, teamService: &teamtest.FakeService{}, expectedStatus: http.StatusForbidden, }, { - name: "should fetch global user permissions when user is not a member of the target org", - targetOrgId: 2, - targerOrgPermissions: []accesscontrol.Permission{{Action: "users:read", Scope: "users:*"}}, - evaluator: accesscontrol.EvalPermission("users:read", "users:*"), - accessControl: ac, - userCache: &usertest.FakeUserService{ - GetSignedInUserFn: func(ctx context.Context, query *user.GetSignedInUserQuery) (*user.SignedInUser, error) { - return &user.SignedInUser{UserID: 1, OrgID: -1, Permissions: map[int64]map[string][]string{}}, nil - }, + name: "should fetch global user permissions when user is not a member of the target org", + targetOrgId: 2, + evaluator: accesscontrol.EvalPermission("users:read", "users:*"), + accessControl: ac, + ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:write": {"users:*"}}}}, + userIdentities: []*authn.Identity{ + {ID: "1", OrgID: -1, Permissions: map[int64]map[string][]string{}}, + {ID: "1", OrgID: accesscontrol.GlobalOrgID, Permissions: map[int64]map[string][]string{accesscontrol.GlobalOrgID: {"users:read": {"users:*"}}}}, }, + authnErrors: []error{nil, nil}, + expectedStatus: http.StatusOK, + }, + { + name: "should fail if user is not a member of the target org and doesn't have the right permissions globally", + targetOrgId: 2, + evaluator: accesscontrol.EvalPermission("users:read", "users:*"), + accessControl: ac, ctxSignedInUser: &user.SignedInUser{UserID: 1, OrgID: 1, Permissions: map[int64]map[string][]string{1: {"users:write": {"users:*"}}}}, - expectedStatus: http.StatusOK, + userIdentities: []*authn.Identity{ + {ID: "1", OrgID: -1, Permissions: map[int64]map[string][]string{}}, + {ID: "1", OrgID: accesscontrol.GlobalOrgID, Permissions: map[int64]map[string][]string{accesscontrol.GlobalOrgID: {"folders:read": {"folders:*"}}}}, + }, + authnErrors: []error{nil, nil}, + expectedStatus: http.StatusForbidden, }, } @@ -194,9 +163,16 @@ func TestAuthorizeInOrgMiddleware(t *testing.T) { Permissions: map[int64]map[string][]string{}, } expectedIdentity.Permissions[tc.targetOrgId] = accesscontrol.GroupScopesByAction(tc.targerOrgPermissions) + var expectedErr error + if len(tc.authnErrors) > 0 { + expectedErr = tc.authnErrors[0] + } authnService := &authntest.FakeService{ - ExpectedIdentity: expectedIdentity, + ExpectedIdentity: expectedIdentity, + ExpectedIdentities: tc.userIdentities, + ExpectedErr: expectedErr, + ExpectedErrs: tc.authnErrors, } var orgIDGetter accesscontrol.OrgIDGetter diff --git a/pkg/services/accesscontrol/middleware.go b/pkg/services/accesscontrol/middleware.go index 5a2aaf024d4..3b3b05b0d5a 100644 --- a/pkg/services/accesscontrol/middleware.go +++ b/pkg/services/accesscontrol/middleware.go @@ -205,6 +205,10 @@ func AuthorizeInOrgMiddleware(ac AccessControl, authnService authn.Service) func var orgUser identity.Requester = c.SignedInUser if targetOrgID != c.SignedInUser.GetOrgID() { orgUser, err = authnService.ResolveIdentity(c.Req.Context(), targetOrgID, c.SignedInUser.GetID()) + if err == nil && orgUser.GetOrgID() == NoOrgID { + // User is not a member of the target org, so only their global permissions are relevant + orgUser, err = authnService.ResolveIdentity(c.Req.Context(), GlobalOrgID, c.SignedInUser.GetID()) + } if err != nil { deny(c, nil, fmt.Errorf("failed to authenticate user in target org: %w", err)) return From 7bee9d5e0f8d0c836c4fb493e779744cf14b0554 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 16:19:49 +0000 Subject: [PATCH 218/229] Update dependency knip to v5.27.3 --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 088ddc547a6..5e760b29ee5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21611,8 +21611,8 @@ __metadata: linkType: hard "knip@npm:^5.10.0": - version: 5.27.2 - resolution: "knip@npm:5.27.2" + version: 5.27.3 + resolution: "knip@npm:5.27.3" dependencies: "@nodelib/fs.walk": "npm:1.2.8" "@snyk/github-codeowners": "npm:1.1.0" @@ -21636,7 +21636,7 @@ __metadata: bin: knip: bin/knip.js knip-bun: bin/knip-bun.js - checksum: 10/65023f970b400e66b6d5edb7262409647399cd0f6706c54dd70f923b44776b954d3c639495a5bd6e2d82ab8c6b700fced8c0d350b34683417417394c5c8815a1 + checksum: 10/b043e5d6e77d32c078961d33f734040082092e14a06e525efba27cfa893d6ce2251c0951245806151203ee32461d2c50cfea2f9b4a5d9bd9fcf1755f2cdef438 languageName: node linkType: hard From 022892ef720240e48e2e41e4bb0c5b5961db300c Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Thu, 22 Aug 2024 13:05:12 +0300 Subject: [PATCH 219/229] Routing: Add CompatRouter to tests (#92114) * Add CompatRouter * Fix tests * Add CompatRouter to TestProvider * Use findBy * Remove AppChromeService * Remove historyOptions * Routing: Fix alerting/test utils issues from react compat router usage (#92127) --------- Co-authored-by: Tom Ratcliffe --- .../unified/RedirectToRuleViewer.test.tsx | 6 +++++ .../unified/RuleEditorGrafanaRules.test.tsx | 2 +- .../alerting/unified/RuleList.test.tsx | 3 ++- .../SharePublicDashboard.test.tsx | 2 +- public/app/features/scopes/testUtils.tsx | 24 ++++--------------- public/test/helpers/TestProvider.tsx | 7 ++++-- public/test/test-utils.tsx | 20 +++++++++++----- 7 files changed, 34 insertions(+), 30 deletions(-) diff --git a/public/app/features/alerting/unified/RedirectToRuleViewer.test.tsx b/public/app/features/alerting/unified/RedirectToRuleViewer.test.tsx index aae12f66c77..3372d095755 100644 --- a/public/app/features/alerting/unified/RedirectToRuleViewer.test.tsx +++ b/public/app/features/alerting/unified/RedirectToRuleViewer.test.tsx @@ -76,6 +76,9 @@ describe('Redirect to Rule viewer', () => { }); it('should properly decode rule name', () => { + // TODO: Fix console warning that happens once CompatRouter is wrapped around this component render + jest.spyOn(console, 'warn').mockImplementation(); + const rulesMatchingSpy = jest.spyOn(combinedRuleHooks, 'useCloudCombinedRulesMatching').mockReturnValue({ rules: [mockedRules[0]], loading: false, @@ -112,6 +115,9 @@ describe('Redirect to Rule viewer', () => { }); it('should properly decode source name', () => { + // TODO: Fix console warning that happens once CompatRouter is wrapped around this component render + jest.spyOn(console, 'warn').mockImplementation(); + const rulesMatchingSpy = jest.spyOn(combinedRuleHooks, 'useCloudCombinedRulesMatching').mockReturnValue({ rules: [mockedRules[0]], loading: false, diff --git a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx index ebd179642d5..aac162157b9 100644 --- a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx @@ -120,7 +120,7 @@ describe('RuleEditor grafana managed rules', () => { const folderInput = await ui.inputs.folder.find(); await clickSelectOption(folderInput, 'Folder A'); const groupInput = await ui.inputs.group.find(); - await userEvent.click(byRole('combobox').get(groupInput)); + await userEvent.click(await byRole('combobox').find(groupInput)); await clickSelectOption(groupInput, grafanaRulerGroup.name); await userEvent.type(ui.inputs.annotationValue(1).get(), 'some description'); diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 2e3e8e56ea4..1eb97e68b1f 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -92,7 +92,8 @@ const renderRuleList = () => { return render( - + , + { renderWithRouter: false } ); }; diff --git a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx index c19af9d767f..a0cf8085e08 100644 --- a/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx +++ b/public/app/features/dashboard/components/ShareModal/SharePublicDashboard/SharePublicDashboard.test.tsx @@ -136,7 +136,7 @@ const alertTests = () => { }); await renderSharePublicDashboard({ dashboard }); - expect(screen.queryByTestId(selectors.UnsupportedDataSourcesWarningAlert)).toBeInTheDocument(); + expect(await screen.findByTestId(selectors.UnsupportedDataSourcesWarningAlert)).toBeInTheDocument(); }); }; diff --git a/public/app/features/scopes/testUtils.tsx b/public/app/features/scopes/testUtils.tsx index 1ab33d8e7ef..291007d62a3 100644 --- a/public/app/features/scopes/testUtils.tsx +++ b/public/app/features/scopes/testUtils.tsx @@ -1,6 +1,6 @@ import { screen } from '@testing-library/react'; import { KBarProvider } from 'kbar'; -import { getWrapper, render } from 'test/test-utils'; +import { render } from 'test/test-utils'; import { Scope, ScopeDashboardBinding, ScopeNode } from '@grafana/data'; import { @@ -16,10 +16,8 @@ import { VizPanel, } from '@grafana/scenes'; import { AppChrome } from 'app/core/components/AppChrome/AppChrome'; -import { AppChromeService } from 'app/core/components/AppChrome/AppChromeService'; import { DashboardControls } from 'app/features/dashboard-scene/scene//DashboardControls'; import { DashboardScene } from 'app/features/dashboard-scene/scene/DashboardScene'; -import { configureStore } from 'app/store/configureStore'; import { ScopesFacade } from './ScopesFacadeScene'; import { scopesDashboardsScene, scopesSelectorScene } from './instance'; @@ -464,24 +462,12 @@ export function buildTestScene(overrides: Partial = {}) { } export function renderDashboard(dashboardScene: DashboardScene) { - const store = configureStore(); - const chrome = new AppChromeService(); - chrome.update({ chromeless: false }); - const Wrapper = getWrapper({ store, renderWithRouter: true, grafanaContext: { chrome } }); - return render( - - - - - - , - { - historyOptions: { - initialEntries: ['/'], - }, - } + + + + ); } diff --git a/public/test/helpers/TestProvider.tsx b/public/test/helpers/TestProvider.tsx index 7fc3861693c..2e7f21043dc 100644 --- a/public/test/helpers/TestProvider.tsx +++ b/public/test/helpers/TestProvider.tsx @@ -2,6 +2,7 @@ import { Store } from '@reduxjs/toolkit'; import * as React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; +import { CompatRouter } from 'react-router-dom-v5-compat'; import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; import { locationService } from '@grafana/runtime'; @@ -35,8 +36,10 @@ export function TestProvider(props: Props) { - {children} - + + {children} + + diff --git a/public/test/test-utils.tsx b/public/test/test-utils.tsx index 1e6e5cee3b0..2dbc22267d5 100644 --- a/public/test/test-utils.tsx +++ b/public/test/test-utils.tsx @@ -6,6 +6,7 @@ import { Fragment, PropsWithChildren } from 'react'; import * as React from 'react'; import { Provider } from 'react-redux'; import { Router } from 'react-router-dom'; +import { CompatRouter } from 'react-router-dom-v5-compat'; import { getGrafanaContextMock } from 'test/mocks/getGrafanaContextMock'; import { HistoryWrapper, LocationServiceProvider, setLocationService } from '@grafana/runtime'; @@ -49,10 +50,6 @@ const getWrapper = ({ grafanaContext?: Partial; }) => { const reduxStore = store || configureStore(); - /** - * Conditional router - either a MemoryRouter or just a Fragment - */ - const PotentialRouter = renderWithRouter ? Router : Fragment; // Create a fresh location service for each test - otherwise we run the risk // of it being stateful in between runs @@ -60,6 +57,15 @@ const getWrapper = ({ const locationService = new HistoryWrapper(history); setLocationService(locationService); + /** + * Conditional router - either a MemoryRouter or just a Fragment + */ + const PotentialRouter = renderWithRouter + ? ({ children }: PropsWithChildren) => {children} + : ({ children }: PropsWithChildren) => {children}; + + const PotentialCompatRouter = renderWithRouter ? CompatRouter : Fragment; + const context = { ...getGrafanaContextMock(), ...grafanaContext, @@ -73,9 +79,11 @@ const getWrapper = ({ return ( - + - {children} + + {children} + From 94a119ac635755fdbe9c90759b5e9c4e507a98e5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 09:40:07 +0000 Subject: [PATCH 220/229] Update dependency ol-ext to v4.0.23 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 73e4fdee5a9..1f935d26de6 100644 --- a/package.json +++ b/package.json @@ -349,7 +349,7 @@ "nanoid": "^5.0.4", "node-forge": "^1.3.1", "ol": "7.4.0", - "ol-ext": "4.0.21", + "ol-ext": "4.0.23", "pluralize": "^8.0.0", "prismjs": "1.29.0", "rc-slider": "10.6.2", diff --git a/yarn.lock b/yarn.lock index 5e760b29ee5..aa3491ef4fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18641,7 +18641,7 @@ __metadata: node-notifier: "npm:10.0.1" nx: "npm:19.2.0" ol: "npm:7.4.0" - ol-ext: "npm:4.0.21" + ol-ext: "npm:4.0.23" pluralize: "npm:^8.0.0" postcss: "npm:8.4.41" postcss-loader: "npm:8.1.1" @@ -24503,12 +24503,12 @@ __metadata: languageName: node linkType: hard -"ol-ext@npm:4.0.21": - version: 4.0.21 - resolution: "ol-ext@npm:4.0.21" +"ol-ext@npm:4.0.23": + version: 4.0.23 + resolution: "ol-ext@npm:4.0.23" peerDependencies: ol: ">= 5.3.0" - checksum: 10/b09b0837b69f85bb366bd97698246d38878e8e50a632699596606a7c16778223c9bb88f332f13c4a17ee0b55dd6e00206bc75979615cd72365824890d4f18c35 + checksum: 10/035aaad001191db3f8146d2b2a2f44c9e4ba86b1592d90d0c8d9b38404a7cbb37a94876cd98f212b2e61f114b059a8cd9cdce967d820ae1964560310975d8657 languageName: node linkType: hard From 0176ead117668390d1dd12add7909c2b1c9971c5 Mon Sep 17 00:00:00 2001 From: Aaron Godin Date: Thu, 22 Aug 2024 05:26:46 -0500 Subject: [PATCH 221/229] feat: Add new read filtering to datasources guardian (#91345) * feat: Add new read filtering to datasources guardian * Apply suggestion to use datasources read guardian check for frontend settings --------- Co-authored-by: Eric Leijonmarck --- pkg/api/datasources.go | 2 +- pkg/api/frontendsettings.go | 2 +- pkg/services/datasources/guardian/allow_guardian.go | 4 ++++ pkg/services/datasources/guardian/provider.go | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index bb79dd6aee7..2a6b3bfd4b8 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -54,7 +54,7 @@ func (hs *HTTPServer) GetDataSources(c *contextmodel.ReqContext) response.Respon return response.Error(http.StatusInternalServerError, "Failed to query datasources", err) } - filtered, err := hs.dsGuardian.New(c.SignedInUser.OrgID, c.SignedInUser).FilterDatasourcesByQueryPermissions(dataSources) + filtered, err := hs.dsGuardian.New(c.SignedInUser.OrgID, c.SignedInUser).FilterDatasourcesByReadPermissions(dataSources) if err != nil { return response.Error(http.StatusInternalServerError, "Failed to query datasources", err) } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 9bf8c35ef75..8f08f6528de 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -410,7 +410,7 @@ func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlug // If RBAC is enabled, it will filter out all datasources for a public user, so we need to skip it orgDataSources = dataSources } else { - filtered, err := hs.dsGuardian.New(c.SignedInUser.OrgID, c.SignedInUser).FilterDatasourcesByQueryPermissions(dataSources) + filtered, err := hs.dsGuardian.New(c.SignedInUser.OrgID, c.SignedInUser).FilterDatasourcesByReadPermissions(dataSources) if err != nil { return nil, err } diff --git a/pkg/services/datasources/guardian/allow_guardian.go b/pkg/services/datasources/guardian/allow_guardian.go index add832b43cb..a6a1e5ac257 100644 --- a/pkg/services/datasources/guardian/allow_guardian.go +++ b/pkg/services/datasources/guardian/allow_guardian.go @@ -17,3 +17,7 @@ func (n AllowGuardian) CanQuery(datasourceID int64) (bool, error) { func (n AllowGuardian) FilterDatasourcesByQueryPermissions(ds []*datasources.DataSource) ([]*datasources.DataSource, error) { return ds, nil } + +func (n AllowGuardian) FilterDatasourcesByReadPermissions(ds []*datasources.DataSource) ([]*datasources.DataSource, error) { + return ds, nil +} diff --git a/pkg/services/datasources/guardian/provider.go b/pkg/services/datasources/guardian/provider.go index 261d7d02755..bc32282f0a1 100644 --- a/pkg/services/datasources/guardian/provider.go +++ b/pkg/services/datasources/guardian/provider.go @@ -11,6 +11,7 @@ type DatasourceGuardianProvider interface { type DatasourceGuardian interface { CanQuery(datasourceID int64) (bool, error) + FilterDatasourcesByReadPermissions([]*datasources.DataSource) ([]*datasources.DataSource, error) FilterDatasourcesByQueryPermissions([]*datasources.DataSource) ([]*datasources.DataSource, error) } From 7b9843bc13b13dfd236f134171ba074dc81b5966 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:12:30 +0000 Subject: [PATCH 222/229] Update babel monorepo to v7.25.4 --- package.json | 4 +- packages/grafana-flamegraph/package.json | 2 +- yarn.lock | 216 ++++++++++------------- 3 files changed, 101 insertions(+), 121 deletions(-) diff --git a/package.json b/package.json index 1f935d26de6..fb6beb3dadd 100644 --- a/package.json +++ b/package.json @@ -68,8 +68,8 @@ }, "devDependencies": { "@babel/core": "7.25.2", - "@babel/preset-env": "7.25.3", - "@babel/runtime": "7.25.0", + "@babel/preset-env": "7.25.4", + "@babel/runtime": "7.25.4", "@betterer/betterer": "5.4.0", "@betterer/cli": "5.4.0", "@betterer/eslint": "5.4.0", diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 77236034aa2..aca8388c771 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -57,7 +57,7 @@ }, "devDependencies": { "@babel/core": "7.25.2", - "@babel/preset-env": "7.25.3", + "@babel/preset-env": "7.25.4", "@babel/preset-react": "7.24.7", "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "15.2.3", diff --git a/yarn.lock b/yarn.lock index aa3491ef4fb..32092388817 100644 --- a/yarn.lock +++ b/yarn.lock @@ -92,10 +92,10 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.2": - version: 7.25.2 - resolution: "@babel/compat-data@npm:7.25.2" - checksum: 10/fd61de9303db3177fc98173571f81f3f551eac5c9f839c05ad02818b11fe77a74daa632abebf7f423fbb4a29976ae9141e0d2bd7517746a0ff3d74cb659ad33a +"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.2, @babel/compat-data@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/compat-data@npm:7.25.4" + checksum: 10/d37a8936cc355a9ca3050102e03d179bdae26bd2e5c99a977637376c192b23637a039795f153c849437a086727628c9860e2c6af92d7151396e2362c09176337 languageName: node linkType: hard @@ -122,15 +122,15 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.22.9, @babel/generator@npm:^7.24.4, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.7.2": - version: 7.25.0 - resolution: "@babel/generator@npm:7.25.0" +"@babel/generator@npm:^7.22.9, @babel/generator@npm:^7.24.4, @babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.4, @babel/generator@npm:^7.7.2": + version: 7.25.4 + resolution: "@babel/generator@npm:7.25.4" dependencies: - "@babel/types": "npm:^7.25.0" + "@babel/types": "npm:^7.25.4" "@jridgewell/gen-mapping": "npm:^0.3.5" "@jridgewell/trace-mapping": "npm:^0.3.25" jsesc: "npm:^2.5.1" - checksum: 10/de3ce2ae7aa0c9585260556ca5a81ce2ce6b8269e3260d7bb4e47a74661af715184ca6343e9906c22e4dd3eed5ce39977dfaf6cded4d2d8968fa096c7cf66697 + checksum: 10/35b05e1f230649469c64971e034b5101079c37d23f8cc658323f1209e39daf58d29ec4ce6de1d6d31dacddd39ffbf6b7e9a2b124d4f6b360a5f7046ae10fbaf4 languageName: node linkType: hard @@ -166,26 +166,24 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.24.5, @babel/helper-create-class-features-plugin@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-create-class-features-plugin@npm:7.24.7" +"@babel/helper-create-class-features-plugin@npm:^7.24.5, @babel/helper-create-class-features-plugin@npm:^7.24.7, @babel/helper-create-class-features-plugin@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/helper-create-class-features-plugin@npm:7.25.4" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-function-name": "npm:^7.24.7" - "@babel/helper-member-expression-to-functions": "npm:^7.24.7" + "@babel/helper-member-expression-to-functions": "npm:^7.24.8" "@babel/helper-optimise-call-expression": "npm:^7.24.7" - "@babel/helper-replace-supers": "npm:^7.24.7" + "@babel/helper-replace-supers": "npm:^7.25.0" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7" - "@babel/helper-split-export-declaration": "npm:^7.24.7" + "@babel/traverse": "npm:^7.25.4" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/8ecb1c2acc808e1e0c21dccc7ea6899de9a140cb1856946800176b4784de6fccd575661fbff7744bb895d01aa6956ce963446b8577c4c2334293ba5579d5cdb9 + checksum: 10/47218da9fd964af30d41f0635d9e33eed7518e03aa8f10c3eb8a563bb2c14f52be3e3199db5912ae0e26058c23bb511c811e565c55ecec09427b04b867ed13c2 languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7, @babel/helper-create-regexp-features-plugin@npm:^7.25.0": +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7, @babel/helper-create-regexp-features-plugin@npm:^7.25.0, @babel/helper-create-regexp-features-plugin@npm:^7.25.2": version: 7.25.2 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.2" dependencies: @@ -198,9 +196,9 @@ __metadata: languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.6.1": - version: 0.6.1 - resolution: "@babel/helper-define-polyfill-provider@npm:0.6.1" +"@babel/helper-define-polyfill-provider@npm:^0.6.1, @babel/helper-define-polyfill-provider@npm:^0.6.2": + version: 0.6.2 + resolution: "@babel/helper-define-polyfill-provider@npm:0.6.2" dependencies: "@babel/helper-compilation-targets": "npm:^7.22.6" "@babel/helper-plugin-utils": "npm:^7.22.5" @@ -209,20 +207,11 @@ __metadata: resolve: "npm:^1.14.2" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/316e7c0f05d2ae233d5fbb622c6339436da8d2b2047be866b64a16e6996c078a23b4adfebbdb33bc6a9882326a6cc20b95daa79a5e0edc92e9730e36d45fa523 - languageName: node - linkType: hard - -"@babel/helper-environment-visitor@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-environment-visitor@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10/079d86e65701b29ebc10baf6ed548d17c19b808a07aa6885cc141b690a78581b180ee92b580d755361dc3b16adf975b2d2058b8ce6c86675fcaf43cf22f2f7c6 + checksum: 10/bb32ec12024d3f16e70641bc125d2534a97edbfdabbc9f69001ec9c4ce46f877c7a224c566aa6c8c510c3b0def2e43dc4433bf6a40896ba5ce0cef4ea5ccbcff languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.0.0, @babel/helper-function-name@npm:^7.24.7": +"@babel/helper-function-name@npm:^7.0.0": version: 7.24.7 resolution: "@babel/helper-function-name@npm:7.24.7" dependencies: @@ -232,7 +221,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.0.0, @babel/helper-member-expression-to-functions@npm:^7.24.7, @babel/helper-member-expression-to-functions@npm:^7.24.8": +"@babel/helper-member-expression-to-functions@npm:^7.0.0, @babel/helper-member-expression-to-functions@npm:^7.24.8": version: 7.24.8 resolution: "@babel/helper-member-expression-to-functions@npm:7.24.8" dependencies: @@ -328,15 +317,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-split-export-declaration@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-split-export-declaration@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 10/ff04a3071603c87de0d6ee2540b7291ab36305b329bd047cdbb6cbd7db335a12f9a77af1cf708779f75f13c4d9af46093c00b34432e50b2411872c658d1a2e5e - languageName: node - linkType: hard - "@babel/helper-string-parser@npm:^7.24.8": version: 7.24.8 resolution: "@babel/helper-string-parser@npm:7.24.8" @@ -391,14 +371,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.3": - version: 7.25.3 - resolution: "@babel/parser@npm:7.25.3" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/parser@npm:7.25.4" dependencies: - "@babel/types": "npm:^7.25.2" + "@babel/types": "npm:^7.25.4" bin: parser: ./bin/babel-parser.js - checksum: 10/7bd57e89110bdc9cffe0ef2f2286f1cfb9bbb3aa1d9208c287e0bf6a1eb4cfe6ab33958876ebc59aafcbe3e2381c4449240fc7cc2ff32b79bc9db89cd52fc779 + checksum: 10/343b8a76c43549e370fe96f4f6d564382a6cdff60e9c3b8a594c51e4cefd58ec9945e82e8c4dfbf15ac865a04e4b29806531440760748e28568e6aec21bc9cb5 languageName: node linkType: hard @@ -736,17 +716,17 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.0" +"@babel/plugin-transform-async-generator-functions@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.4" dependencies: "@babel/helper-plugin-utils": "npm:^7.24.8" "@babel/helper-remap-async-to-generator": "npm:^7.25.0" "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/traverse": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/c65757490005234719a9614dbaf5004ca815612eff251edf95d4149fb74f42ebf91ff079f6b3594b6aa93eec6f4b6d2cda9f2c924f6217bb0422896be58ed0fe + checksum: 10/0004d910bbec3ef916acf5c7cf8b11671e65d2dd425a82f1101838b9b6243bfdf9578335584d9dedd20acc162796b687930e127c6042484e05b758af695e6cb8 languageName: node linkType: hard @@ -785,15 +765,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-properties@npm:^7.22.5, @babel/plugin-transform-class-properties@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-class-properties@npm:7.24.7" +"@babel/plugin-transform-class-properties@npm:^7.22.5, @babel/plugin-transform-class-properties@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-class-properties@npm:7.25.4" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-create-class-features-plugin": "npm:^7.25.4" + "@babel/helper-plugin-utils": "npm:^7.24.8" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/1c6f645dd3889257028f27bfbb04526ac7676763a923fc8203aa79aa5232820e0201cb858c73b684b1922327af10304121ac013c7b756876d54560a9c1a7bc79 + checksum: 10/203a21384303d66fb5d841b77cba8b8994623ff4d26d208e3d05b36858c4919626a8d74871fa4b9195310c2e7883bf180359c4f5a76481ea55190c224d9746f4 languageName: node linkType: hard @@ -810,19 +790,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.25.0": - version: 7.25.0 - resolution: "@babel/plugin-transform-classes@npm:7.25.0" +"@babel/plugin-transform-classes@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-classes@npm:7.25.4" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.24.7" - "@babel/helper-compilation-targets": "npm:^7.24.8" + "@babel/helper-compilation-targets": "npm:^7.25.2" "@babel/helper-plugin-utils": "npm:^7.24.8" "@babel/helper-replace-supers": "npm:^7.25.0" - "@babel/traverse": "npm:^7.25.0" + "@babel/traverse": "npm:^7.25.4" globals: "npm:^11.1.0" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/59aeb33b91e462a9b01cc9691c6a27e6601c5b76d83e3e4f95fef4086c6561e3557597847fe5243006542723fe4288d8fa6824544b1d94bb3104438f4fd96ebc + checksum: 10/17db5889803529bec366c6f0602687fdd605c2fec8cb6fe918261cb55cd89e9d8c9aa2aa6f3fd64d36492ce02d7d0752b09a284b0f833c1185f7dad9b9506310 languageName: node linkType: hard @@ -1163,15 +1143,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-methods@npm:^7.22.5, @babel/plugin-transform-private-methods@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-private-methods@npm:7.24.7" +"@babel/plugin-transform-private-methods@npm:^7.22.5, @babel/plugin-transform-private-methods@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-private-methods@npm:7.25.4" dependencies: - "@babel/helper-create-class-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-create-class-features-plugin": "npm:^7.25.4" + "@babel/helper-plugin-utils": "npm:^7.24.8" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/5338df2aae53c43e6a7ea0c44f20a1100709778769c7e42d4901a61945c3200ba0e7fca83832f48932423a68528219fbea233cb5b8741a2501fdecbacdc08292 + checksum: 10/d5c29ba121d6ce40e8055a632c32e69006c513607145a29701f93b416a8c53a60e53565df417218e2d8b7f1ba73adb837601e8e9d0a3215da50e4c9507f9f1fa languageName: node linkType: hard @@ -1377,15 +1357,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-sets-regex@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.24.7" +"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.4" dependencies: - "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7" - "@babel/helper-plugin-utils": "npm:^7.24.7" + "@babel/helper-create-regexp-features-plugin": "npm:^7.25.2" + "@babel/helper-plugin-utils": "npm:^7.24.8" peerDependencies: "@babel/core": ^7.0.0 - checksum: 10/183b72d5987dc93f9971667ce3f26d28b0e1058e71b129733dd9d5282aecba4c062b67c9567526780d2defd2bfbf950ca58d8306dc90b2761fd1e960d867ddb7 + checksum: 10/d5d07d17932656fa4d62fd67ecaa1a5e4c2e92365a924f1a2a8cf8108762f137a30cd55eb3a7d0504258f27a19ad0decca6b62a5c37a5aada709cbb46c4a871f languageName: node linkType: hard @@ -1399,11 +1379,11 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:7.25.3, @babel/preset-env@npm:^7.24.4": - version: 7.25.3 - resolution: "@babel/preset-env@npm:7.25.3" +"@babel/preset-env@npm:7.25.4, @babel/preset-env@npm:^7.24.4": + version: 7.25.4 + resolution: "@babel/preset-env@npm:7.25.4" dependencies: - "@babel/compat-data": "npm:^7.25.2" + "@babel/compat-data": "npm:^7.25.4" "@babel/helper-compilation-targets": "npm:^7.25.2" "@babel/helper-plugin-utils": "npm:^7.24.8" "@babel/helper-validator-option": "npm:^7.24.8" @@ -1432,13 +1412,13 @@ __metadata: "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" "@babel/plugin-transform-arrow-functions": "npm:^7.24.7" - "@babel/plugin-transform-async-generator-functions": "npm:^7.25.0" + "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4" "@babel/plugin-transform-async-to-generator": "npm:^7.24.7" "@babel/plugin-transform-block-scoped-functions": "npm:^7.24.7" "@babel/plugin-transform-block-scoping": "npm:^7.25.0" - "@babel/plugin-transform-class-properties": "npm:^7.24.7" + "@babel/plugin-transform-class-properties": "npm:^7.25.4" "@babel/plugin-transform-class-static-block": "npm:^7.24.7" - "@babel/plugin-transform-classes": "npm:^7.25.0" + "@babel/plugin-transform-classes": "npm:^7.25.4" "@babel/plugin-transform-computed-properties": "npm:^7.24.7" "@babel/plugin-transform-destructuring": "npm:^7.24.8" "@babel/plugin-transform-dotall-regex": "npm:^7.24.7" @@ -1466,7 +1446,7 @@ __metadata: "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7" "@babel/plugin-transform-optional-chaining": "npm:^7.24.8" "@babel/plugin-transform-parameters": "npm:^7.24.7" - "@babel/plugin-transform-private-methods": "npm:^7.24.7" + "@babel/plugin-transform-private-methods": "npm:^7.25.4" "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7" "@babel/plugin-transform-property-literals": "npm:^7.24.7" "@babel/plugin-transform-regenerator": "npm:^7.24.7" @@ -1479,16 +1459,16 @@ __metadata: "@babel/plugin-transform-unicode-escapes": "npm:^7.24.7" "@babel/plugin-transform-unicode-property-regex": "npm:^7.24.7" "@babel/plugin-transform-unicode-regex": "npm:^7.24.7" - "@babel/plugin-transform-unicode-sets-regex": "npm:^7.24.7" + "@babel/plugin-transform-unicode-sets-regex": "npm:^7.25.4" "@babel/preset-modules": "npm:0.1.6-no-external-plugins" babel-plugin-polyfill-corejs2: "npm:^0.4.10" - babel-plugin-polyfill-corejs3: "npm:^0.10.4" + babel-plugin-polyfill-corejs3: "npm:^0.10.6" babel-plugin-polyfill-regenerator: "npm:^0.6.1" core-js-compat: "npm:^3.37.1" semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/293c32dee33f138d22cea0c0e163b6d79ef3860ac269921a438edb4adbfa53976ce2cd3f7a79408c8e52c852b5feda45abdbc986a54e9d9aa0b6680d7a371a58 + checksum: 10/45ca65bdc7fa11ca51167804052460eda32bf2e6620c7ba998e2d95bc867595913532ee7d748e97e808eabcc66aabe796bd75c59014d996ec8183fa5a7245862 languageName: node linkType: hard @@ -1581,12 +1561,12 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.25.0, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": - version: 7.25.0 - resolution: "@babel/runtime@npm:7.25.0" +"@babel/runtime@npm:7.25.4, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": + version: 7.25.4 + resolution: "@babel/runtime@npm:7.25.4" dependencies: regenerator-runtime: "npm:^0.14.0" - checksum: 10/6870e9e0e9125075b3aeba49a266f442b10820bfc693019eb6c1785c5a0edbe927e98b8238662cdcdba17842107c040386c3b69f39a0a3b217f9d00ffe685b27 + checksum: 10/70d2a420c24a3289ea6c4addaf3a1c4186bc3d001c92445faa3cd7601d7d2fbdb32c63b3a26b9771e20ff2f511fa76b726bf256f823cdb95bc37b8eadbd02f70 languageName: node linkType: hard @@ -1601,29 +1581,29 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.24.1, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3": - version: 7.25.3 - resolution: "@babel/traverse@npm:7.25.3" +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.22.8, @babel/traverse@npm:^7.24.1, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.25.4": + version: 7.25.4 + resolution: "@babel/traverse@npm:7.25.4" dependencies: "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.25.0" - "@babel/parser": "npm:^7.25.3" + "@babel/generator": "npm:^7.25.4" + "@babel/parser": "npm:^7.25.4" "@babel/template": "npm:^7.25.0" - "@babel/types": "npm:^7.25.2" + "@babel/types": "npm:^7.25.4" debug: "npm:^4.3.1" globals: "npm:^11.1.0" - checksum: 10/fba34f323e17fa83372fc290bc12413a50e2f780a86c7d8b1875c594b6be2857867804de5d52ab10a78a9cae29e1b09ea15d85ad63671ce97d79c40650282bb9 + checksum: 10/a85c16047ab8e454e2e758c75c31994cec328bd6d8b4b22e915fa7393a03b3ab96d1218f43dc7ef77c957cc488dc38100bdf504d08a80a131e89b2e49cfa2be5 languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": - version: 7.25.2 - resolution: "@babel/types@npm:7.25.2" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": + version: 7.25.4 + resolution: "@babel/types@npm:7.25.4" dependencies: "@babel/helper-string-parser": "npm:^7.24.8" "@babel/helper-validator-identifier": "npm:^7.24.7" to-fast-properties: "npm:^2.0.0" - checksum: 10/ccf5399db1dcd6dd87b84a6f7bc8dd241e04a326f4f038c973c26ccb69cd360c8f2276603f584c58fd94da95229313060b27baceb0d9b18a435742d3f616afd1 + checksum: 10/d4a1194612d0a2a6ce9a0be325578b43d74e5f5278c67409468ba0a924341f0ad349ef0245ee8a36da3766efe5cc59cd6bb52547674150f97d8dc4c8cfa5d6b8 languageName: node linkType: hard @@ -3568,7 +3548,7 @@ __metadata: resolution: "@grafana/flamegraph@workspace:packages/grafana-flamegraph" dependencies: "@babel/core": "npm:7.25.2" - "@babel/preset-env": "npm:7.25.3" + "@babel/preset-env": "npm:7.25.4" "@babel/preset-react": "npm:7.24.7" "@emotion/css": "npm:11.11.2" "@grafana/data": "npm:11.3.0-pre" @@ -12234,15 +12214,15 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.10.4": - version: 0.10.4 - resolution: "babel-plugin-polyfill-corejs3@npm:0.10.4" +"babel-plugin-polyfill-corejs3@npm:^0.10.6": + version: 0.10.6 + resolution: "babel-plugin-polyfill-corejs3@npm:0.10.6" dependencies: - "@babel/helper-define-polyfill-provider": "npm:^0.6.1" - core-js-compat: "npm:^3.36.1" + "@babel/helper-define-polyfill-provider": "npm:^0.6.2" + core-js-compat: "npm:^3.38.0" peerDependencies: "@babel/core": ^7.4.0 || ^8.0.0-0 <8.0.0 - checksum: 10/a69ed5a95bb55e9b7ea37307d56113f7e24054d479c15de6d50fa61388b5334bed1f9b6414cde6c575fa910a4de4d1ab4f2d22720967d57c4fec9d1b8f61b355 + checksum: 10/360ac9054a57a18c540059dc627ad5d84d15f79790cb3d84d19a02eec7188c67d08a07db789c3822d6f5df22d918e296d1f27c4055fec2e287d328f09ea8a78a languageName: node linkType: hard @@ -12618,7 +12598,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.2, browserslist@npm:^4.23.0, browserslist@npm:^4.23.1, browserslist@npm:^4.23.3": +"browserslist@npm:^4.0.0, browserslist@npm:^4.21.10, browserslist@npm:^4.21.4, browserslist@npm:^4.22.2, browserslist@npm:^4.23.1, browserslist@npm:^4.23.3": version: 4.23.3 resolution: "browserslist@npm:4.23.3" dependencies: @@ -13850,12 +13830,12 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.36.1, core-js-compat@npm:^3.37.1": - version: 3.37.1 - resolution: "core-js-compat@npm:3.37.1" +"core-js-compat@npm:^3.37.1, core-js-compat@npm:^3.38.0": + version: 3.38.1 + resolution: "core-js-compat@npm:3.38.1" dependencies: - browserslist: "npm:^4.23.0" - checksum: 10/30c6fdbd9ff179cc53951814689b8aabec106e5de6cddfa7a7feacc96b66d415b8eebcf5ec8f7c68ef35c552fe7d39edb8b15b1ce0f27379a272295b6e937061 + browserslist: "npm:^4.23.3" + checksum: 10/4e2f219354fd268895f79486461a12df96f24ed307321482fe2a43529c5a64e7c16bcba654980ba217d603444f5141d43a79058aeac77511085f065c5da72207 languageName: node linkType: hard @@ -18396,8 +18376,8 @@ __metadata: resolution: "grafana@workspace:." dependencies: "@babel/core": "npm:7.25.2" - "@babel/preset-env": "npm:7.25.3" - "@babel/runtime": "npm:7.25.0" + "@babel/preset-env": "npm:7.25.4" + "@babel/runtime": "npm:7.25.4" "@betterer/betterer": "npm:5.4.0" "@betterer/cli": "npm:5.4.0" "@betterer/eslint": "npm:5.4.0" From 3e84b2ba2f2fde104b9f808b1647b854fa0649e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 22 Aug 2024 13:35:24 +0200 Subject: [PATCH 223/229] Dashboard: Fixes closing share modal (#92267) --- public/app/features/dashboard-scene/sharing/ShareModal.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/sharing/ShareModal.tsx b/public/app/features/dashboard-scene/sharing/ShareModal.tsx index 8a325d5d0b9..da4d8c75320 100644 --- a/public/app/features/dashboard-scene/sharing/ShareModal.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareModal.tsx @@ -88,7 +88,12 @@ export class ShareModal extends SceneObjectBase implements Moda } onDismiss = () => { - locationService.partial({ shareView: null }); + if (this.state.panelRef) { + const dashboard = getDashboardSceneFor(this); + dashboard.closeModal(); + } else { + locationService.partial({ shareView: null }); + } }; onChangeTab: ComponentProps['onChangeTab'] = (tab) => { From f188da7b655d30adedcc0aaa0978c99229c953aa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 10:33:20 +0000 Subject: [PATCH 224/229] Update dependency rudder-sdk-js to v2.48.16 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index fb6beb3dadd..f517c7842c7 100644 --- a/package.json +++ b/package.json @@ -219,7 +219,7 @@ "react-test-renderer": "18.2.0", "redux-mock-store": "1.5.4", "rimraf": "5.0.7", - "rudder-sdk-js": "2.48.15", + "rudder-sdk-js": "2.48.16", "sass": "1.77.8", "sass-loader": "14.2.1", "smtp-tester": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index 32092388817..4f6d81eb37f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18672,7 +18672,7 @@ __metadata: regenerator-runtime: "npm:0.14.1" reselect: "npm:5.1.1" rimraf: "npm:5.0.7" - rudder-sdk-js: "npm:2.48.15" + rudder-sdk-js: "npm:2.48.16" rxjs: "npm:7.8.1" sass: "npm:1.77.8" sass-loader: "npm:14.2.1" @@ -28486,10 +28486,10 @@ __metadata: languageName: node linkType: hard -"rudder-sdk-js@npm:2.48.15": - version: 2.48.15 - resolution: "rudder-sdk-js@npm:2.48.15" - checksum: 10/b4c01e1fc3beec5c96a982e443e65ad4a68fe47fff911e5b8f799325ca3994d154019a2512a6969b296079f87a825ccade1c7a7ed7196c5ac31db472d1bb3b6a +"rudder-sdk-js@npm:2.48.16": + version: 2.48.16 + resolution: "rudder-sdk-js@npm:2.48.16" + checksum: 10/849b5d07ce4931b64ca42b36c9c6f701784d210dcdbe284b57bb50f4f7ecf38808ae7b7b227d82a35ed92cb05d9f0f4e4fc003b4d134f5f2e64ea1a21e8c2c1b languageName: node linkType: hard From 1b336e94c8d851c371b46baebb44d58f69a5b0b2 Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Thu, 22 Aug 2024 13:47:00 +0200 Subject: [PATCH 225/229] Chore: Check version compatibilty when installing a plugin (#92200) --- .../grafana-cli/commands/install_command.go | 4 +- pkg/plugins/repo/errors.go | 8 ++++ pkg/plugins/repo/models.go | 14 +++++-- pkg/plugins/repo/service.go | 8 +--- pkg/plugins/repo/service_test.go | 11 +++-- pkg/plugins/repo/version.go | 42 +++++++++++++------ pkg/plugins/repo/version_test.go | 37 +++++++++++++--- 7 files changed, 89 insertions(+), 35 deletions(-) diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index de4ae3f9b7c..42a8317a175 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -134,7 +134,9 @@ func doInstallPlugin(ctx context.Context, pluginID, version string, o pluginInst Logger: services.Logger, }) - compatOpts := repo.NewCompatOpts(services.GrafanaVersion, runtime.GOOS, runtime.GOARCH) + // FIXME: Re-enable grafanaVersion. This check was broken in 10.2 so disabling it for the moment. + // Expected to be re-enabled in 12.x. + compatOpts := repo.NewCompatOpts("", runtime.GOOS, runtime.GOARCH) var archive *repo.PluginArchive var err error diff --git a/pkg/plugins/repo/errors.go b/pkg/plugins/repo/errors.go index 15c0db83068..c275ce43afc 100644 --- a/pkg/plugins/repo/errors.go +++ b/pkg/plugins/repo/errors.go @@ -67,6 +67,10 @@ var ( ErrCorePluginMsg = "plugin {{.Public.PluginID}} is a core plugin and cannot be installed separately" ErrCorePluginBase = errutil.Forbidden("plugin.forbiddenCorePluginInstall"). MustTemplate(ErrCorePluginMsg, errutil.WithPublic(ErrCorePluginMsg)) + + ErrNotCompatibledMsg = "{{.Public.PluginID}} is not compatible with your Grafana version: {{.Public.GrafanaVersion}}" + ErrNotCompatibleBase = errutil.NotFound("plugin.grafanaVersionNotCompatible"). + MustTemplate(ErrNotCompatibledMsg, errutil.WithPublic(ErrNotCompatibledMsg)) ) func ErrVersionUnsupported(pluginID, requestedVersion, systemInfo string) error { @@ -88,3 +92,7 @@ func ErrChecksumMismatch(archiveURL string) error { func ErrCorePlugin(pluginID string) error { return ErrCorePluginBase.Build(errutil.TemplateData{Public: map[string]any{"PluginID": pluginID}}) } + +func ErrNoCompatibleVersions(pluginID, grafanaVersion string) error { + return ErrNotCompatibleBase.Build(errutil.TemplateData{Public: map[string]any{"PluginID": pluginID, "GrafanaVersion": grafanaVersion}}) +} diff --git a/pkg/plugins/repo/models.go b/pkg/plugins/repo/models.go index 85c1519b5c8..73c06259810 100644 --- a/pkg/plugins/repo/models.go +++ b/pkg/plugins/repo/models.go @@ -18,11 +18,17 @@ type PluginVersions struct { } type Version struct { - Version string `json:"version"` - Arch map[string]ArchMeta `json:"packages"` - URL string `json:"url"` + Version string `json:"version"` + Arch map[string]ArchMeta `json:"packages"` + URL string `json:"url"` + CreatedAt string `json:"createdAt"` + IsCompatible *bool `json:"isCompatible,omitempty"` + GrafanaDependency string `json:"grafanaDependency"` } type ArchMeta struct { - SHA256 string `json:"sha256"` + SHA256 string `json:"sha256"` + MD5 string `json:"md5"` + PackageName string `json:"packageName"` + DownloadURL string `json:"downloadUrl"` } diff --git a/pkg/plugins/repo/service.go b/pkg/plugins/repo/service.go index dea206ef5af..59af8d95906 100644 --- a/pkg/plugins/repo/service.go +++ b/pkg/plugins/repo/service.go @@ -3,7 +3,6 @@ package repo import ( "context" "encoding/json" - "errors" "fmt" "net/http" "net/url" @@ -84,12 +83,7 @@ func (m *Manager) PluginVersion(pluginID, version string, compatOpts CompatOpts) return VersionData{}, err } - sysCompatOpts, exists := compatOpts.System() - if !exists { - return VersionData{}, errors.New("no system compatibility requirements set") - } - - compatibleVer, err := SelectSystemCompatibleVersion(m.log, versions, pluginID, version, sysCompatOpts) + compatibleVer, err := SelectSystemCompatibleVersion(m.log, versions, pluginID, version, compatOpts) if err != nil { return VersionData{}, err } diff --git a/pkg/plugins/repo/service_test.go b/pkg/plugins/repo/service_test.go index ba050893420..887fe42868f 100644 --- a/pkg/plugins/repo/service_test.go +++ b/pkg/plugins/repo/service_test.go @@ -174,7 +174,8 @@ func mockPluginVersionsAPI(t *testing.T, data srvData) *httptest.Server { "sha256": "%s" } }, - "url": "%s" + "url": "%s", + "isCompatible": true }] } `, data.version, platform, data.sha, data.url), @@ -192,15 +193,17 @@ func mockPluginVersionsAPI(t *testing.T, data srvData) *httptest.Server { } type versionArg struct { - version string - arch []string + version string + arch []string + isCompatible *bool } func createPluginVersions(versions ...versionArg) []Version { vs := make([]Version, len(versions)) for i, version := range versions { ver := Version{ - Version: version.version, + Version: version.version, + IsCompatible: version.isCompatible, } if version.arch != nil { ver.Arch = map[string]ArchMeta{} diff --git a/pkg/plugins/repo/version.go b/pkg/plugins/repo/version.go index ad3716d1c2f..b589288019b 100644 --- a/pkg/plugins/repo/version.go +++ b/pkg/plugins/repo/version.go @@ -1,6 +1,8 @@ package repo import ( + "errors" + "slices" "strings" "github.com/grafana/grafana/pkg/plugins/log" @@ -19,19 +21,24 @@ type VersionData struct { // returns error if the supplied version does not exist. // returns error if supplied version exists but is not supported. // NOTE: It expects plugin.Versions to be sorted so the newest version is first. -func SelectSystemCompatibleVersion(log log.PrettyLogger, versions []Version, pluginID, version string, compatOpts SystemCompatOpts) (VersionData, error) { +func SelectSystemCompatibleVersion(log log.PrettyLogger, versions []Version, pluginID, version string, compatOpts CompatOpts) (VersionData, error) { version = normalizeVersion(version) - var ver Version - latestForArch, exists := latestSupportedVersion(versions, compatOpts) + sysCompatOpts, exists := compatOpts.System() if !exists { - return VersionData{}, ErrArcNotFound(pluginID, compatOpts.OSAndArch()) + return VersionData{}, errors.New("no system compatibility requirements set") + } + + var ver Version + latestForArch, err := latestSupportedVersion(pluginID, versions, compatOpts) + if err != nil { + return VersionData{}, err } if version == "" { return VersionData{ Version: latestForArch.Version, - Checksum: checksum(latestForArch, compatOpts), + Checksum: checksum(latestForArch, sysCompatOpts), Arch: latestForArch.Arch, URL: latestForArch.URL, }, nil @@ -46,18 +53,18 @@ func SelectSystemCompatibleVersion(log log.PrettyLogger, versions []Version, plu if len(ver.Version) == 0 { log.Debugf("Requested plugin version %s v%s not found but potential fallback version '%s' was found", pluginID, version, latestForArch.Version) - return VersionData{}, ErrVersionNotFound(pluginID, version, compatOpts.OSAndArch()) + return VersionData{}, ErrVersionNotFound(pluginID, version, sysCompatOpts.OSAndArch()) } - if !supportsCurrentArch(ver, compatOpts) { + if !supportsCurrentArch(ver, sysCompatOpts) { log.Debugf("Requested plugin version %s v%s is not supported on your system but potential fallback version '%s' was found", pluginID, version, latestForArch.Version) - return VersionData{}, ErrVersionUnsupported(pluginID, version, compatOpts.OSAndArch()) + return VersionData{}, ErrVersionUnsupported(pluginID, version, sysCompatOpts.OSAndArch()) } return VersionData{ Version: ver.Version, - Checksum: checksum(ver, compatOpts), + Checksum: checksum(ver, sysCompatOpts), Arch: ver.Arch, URL: ver.URL, }, nil @@ -86,13 +93,22 @@ func supportsCurrentArch(version Version, compatOpts SystemCompatOpts) bool { return false } -func latestSupportedVersion(versions []Version, compatOpts SystemCompatOpts) (Version, bool) { +func latestSupportedVersion(pluginID string, versions []Version, compatOpts CompatOpts) (Version, error) { + // First check if the version are compatible with the current Grafana version + versions = slices.DeleteFunc(versions, func(v Version) bool { + return v.IsCompatible != nil && !*v.IsCompatible + }) + if len(versions) == 0 { + return Version{}, ErrNoCompatibleVersions(pluginID, compatOpts.grafanaVersion) + } + + // Then check if the version are compatible with the current system for _, v := range versions { - if supportsCurrentArch(v, compatOpts) { - return v, true + if supportsCurrentArch(v, compatOpts.system) { + return v, nil } } - return Version{}, false + return Version{}, ErrArcNotFound(pluginID, compatOpts.system.OSAndArch()) } func normalizeVersion(version string) string { diff --git a/pkg/plugins/repo/version_test.go b/pkg/plugins/repo/version_test.go index e3ee36ebaa0..d62671e3b88 100644 --- a/pkg/plugins/repo/version_test.go +++ b/pkg/plugins/repo/version_test.go @@ -8,15 +8,25 @@ import ( "github.com/grafana/grafana/pkg/plugins/log" ) +func fakeCompatOpts() CompatOpts { + return NewCompatOpts("7.0.0", "linux", "amd64") +} + func TestSelectSystemCompatibleVersion(t *testing.T) { logger := log.NewTestPrettyLogger() t.Run("Should return error when requested version does not exist", func(t *testing.T) { - _, err := SelectSystemCompatibleVersion(log.NewTestPrettyLogger(), createPluginVersions(versionArg{version: "version"}), "test", "1.1.1", SystemCompatOpts{}) + _, err := SelectSystemCompatibleVersion( + log.NewTestPrettyLogger(), + createPluginVersions(versionArg{version: "version"}), + "test", "1.1.1", fakeCompatOpts()) require.Error(t, err) }) t.Run("Should return error when no version supports current arch", func(t *testing.T) { - _, err := SelectSystemCompatibleVersion(logger, createPluginVersions(versionArg{version: "version", arch: []string{"non-existent"}}), "test", "", SystemCompatOpts{}) + _, err := SelectSystemCompatibleVersion( + logger, + createPluginVersions(versionArg{version: "version", arch: []string{"non-existent"}}), + "test", "", fakeCompatOpts()) require.Error(t, err) }) @@ -24,7 +34,7 @@ func TestSelectSystemCompatibleVersion(t *testing.T) { _, err := SelectSystemCompatibleVersion(logger, createPluginVersions( versionArg{version: "2.0.0"}, versionArg{version: "1.1.1", arch: []string{"non-existent"}}, - ), "test", "1.1.1", SystemCompatOpts{}) + ), "test", "1.1.1", fakeCompatOpts()) require.Error(t, err) }) @@ -32,20 +42,35 @@ func TestSelectSystemCompatibleVersion(t *testing.T) { ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions( versionArg{version: "2.0.0", arch: []string{"non-existent"}}, versionArg{version: "1.0.0"}, - ), "test", "", SystemCompatOpts{}) + ), "test", "", fakeCompatOpts()) require.NoError(t, err) require.Equal(t, "1.0.0", ver.Version) }) t.Run("Should return latest version when no version specified", func(t *testing.T) { - ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions(versionArg{version: "2.0.0"}, versionArg{version: "1.0.0"}), "test", "", SystemCompatOpts{}) + ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions( + versionArg{version: "2.0.0"}, + versionArg{version: "1.0.0"}), + "test", "", fakeCompatOpts()) require.NoError(t, err) require.Equal(t, "2.0.0", ver.Version) }) t.Run("Should return requested version", func(t *testing.T) { - ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions(versionArg{version: "2.0.0"}, versionArg{version: "1.0.0"}), "test", "1.0.0", SystemCompatOpts{}) + ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions( + versionArg{version: "2.0.0"}, + versionArg{version: "1.0.0"}), + "test", "1.0.0", fakeCompatOpts()) require.NoError(t, err) require.Equal(t, "1.0.0", ver.Version) }) + + t.Run("Should return error when requested version is not compatible", func(t *testing.T) { + isCompatible := false + _, err := SelectSystemCompatibleVersion(logger, + createPluginVersions(versionArg{version: "2.0.0", isCompatible: &isCompatible}), + "test", "2.0.0", fakeCompatOpts(), + ) + require.ErrorContains(t, err, "not compatible") + }) } From c315c2719d357823683bb35ad5eddb7b0e8bbc21 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:41:21 +0000 Subject: [PATCH 226/229] Update dependency stylelint to v16.8.2 --- package.json | 2 +- yarn.lock | 92 ++++++++++++++++++++++++++-------------------------- 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index f517c7842c7..d52bcf94340 100644 --- a/package.json +++ b/package.json @@ -224,7 +224,7 @@ "sass-loader": "14.2.1", "smtp-tester": "^2.1.0", "style-loader": "4.0.0", - "stylelint": "16.8.1", + "stylelint": "16.8.2", "stylelint-config-sass-guidelines": "11.1.0", "terser-webpack-plugin": "5.3.10", "testing-library-selector": "0.3.1", diff --git a/yarn.lock b/yarn.lock index 4f6d81eb37f..353a4e60cc0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1804,38 +1804,38 @@ __metadata: languageName: node linkType: hard -"@csstools/css-parser-algorithms@npm:^2.7.1": - version: 2.7.1 - resolution: "@csstools/css-parser-algorithms@npm:2.7.1" +"@csstools/css-parser-algorithms@npm:^3.0.0": + version: 3.0.1 + resolution: "@csstools/css-parser-algorithms@npm:3.0.1" peerDependencies: - "@csstools/css-tokenizer": ^2.4.1 - checksum: 10/939b23652c970dc4af8c20776e5da9e592cae4a590025f07ddb3263799076d4b6cf1bf8c4de97b29780bfa169177a31945effe94d2a11e0972138b5ff7d93654 + "@csstools/css-tokenizer": ^3.0.1 + checksum: 10/02649a70ab7bab1fd000ca1d196ffb93ad3e2e0f36b4aa064f7973cd31edc5f7e63f8eaf7b94d801a0bfd207386b8b23cbe40be6e871c27042b084c3a717349e languageName: node linkType: hard -"@csstools/css-tokenizer@npm:^2.4.1": - version: 2.4.1 - resolution: "@csstools/css-tokenizer@npm:2.4.1" - checksum: 10/a368e5c96d3b11e147f95951e336105480acfa457cdbc6fdf97e8873ff92ab9ee6b4b6224ac1b263f08798802f6b29b8977a502d070f9ab695c9b9905b964198 +"@csstools/css-tokenizer@npm:^3.0.0": + version: 3.0.1 + resolution: "@csstools/css-tokenizer@npm:3.0.1" + checksum: 10/81ae01b2d3ec40ed3dc78f8507cbfdfe1dbc4ae3f8c8e29b8bb4414216a8c7a7a936fa0faa3d11a1e49ad72209aec7c05ad8450a4ffc30ba288aa074b4a0e3b3 languageName: node linkType: hard -"@csstools/media-query-list-parser@npm:^2.1.13": - version: 2.1.13 - resolution: "@csstools/media-query-list-parser@npm:2.1.13" +"@csstools/media-query-list-parser@npm:^3.0.0": + version: 3.0.1 + resolution: "@csstools/media-query-list-parser@npm:3.0.1" peerDependencies: - "@csstools/css-parser-algorithms": ^2.7.1 - "@csstools/css-tokenizer": ^2.4.1 - checksum: 10/4a771d94eb01a23279d493cd668c71ae230b660c1e6ebcff1bec6e959eae6987ece7ce01b094b44afbae8695dc98d8617580d488db16de9ec4a7378ed5adf57f + "@csstools/css-parser-algorithms": ^3.0.1 + "@csstools/css-tokenizer": ^3.0.1 + checksum: 10/794344c67b126ad93d516ab3f01254d44cfa794c3401e34e8cc62ddc7fc13c9ab6c76cb517b643dbda47b57f2eb578c6a11c4a9a4b516d88e260a4016b64ce7f languageName: node linkType: hard -"@csstools/selector-specificity@npm:^3.1.1": - version: 3.1.1 - resolution: "@csstools/selector-specificity@npm:3.1.1" +"@csstools/selector-specificity@npm:^4.0.0": + version: 4.0.0 + resolution: "@csstools/selector-specificity@npm:4.0.0" peerDependencies: - postcss-selector-parser: ^6.0.13 - checksum: 10/3786a6afea97b08ad739ee8f4004f7e0a9e25049cee13af809dbda6462090744012a54bd9275a44712791e8f103f85d21641f14e81799f9dab946b0459a5e1ef + postcss-selector-parser: ^6.1.0 + checksum: 10/7076c1d8af0fba94f06718f87fba5bfea583f39089efa906ae38b5ecd6912d3d5865f7047a871ac524b1057e4c970622b2ade456b90d69fb9393902250057994 languageName: node linkType: hard @@ -18683,7 +18683,7 @@ __metadata: slate-react: "npm:0.22.10" smtp-tester: "npm:^2.1.0" style-loader: "npm:4.0.0" - stylelint: "npm:16.8.1" + stylelint: "npm:16.8.2" stylelint-config-sass-guidelines: "npm:11.1.0" swagger-ui-react: "npm:5.17.14" symbol-observable: "npm:4.0.0" @@ -19535,10 +19535,10 @@ __metadata: languageName: node linkType: hard -"ignore@npm:^5.0.4, ignore@npm:^5.1.1, ignore@npm:^5.1.8, ignore@npm:^5.2.0, ignore@npm:^5.2.4, ignore@npm:^5.3.1": - version: 5.3.1 - resolution: "ignore@npm:5.3.1" - checksum: 10/0a884c2fbc8c316f0b9f92beaf84464253b73230a4d4d286697be45fca081199191ca33e1c2e82d9e5f851f5e9a48a78e25a35c951e7eb41e59f150db3530065 +"ignore@npm:^5.0.4, ignore@npm:^5.1.1, ignore@npm:^5.1.8, ignore@npm:^5.2.0, ignore@npm:^5.2.4, ignore@npm:^5.3.2": + version: 5.3.2 + resolution: "ignore@npm:5.3.2" + checksum: 10/cceb6a457000f8f6a50e1196429750d782afce5680dd878aa4221bd79972d68b3a55b4b1458fc682be978f4d3c6a249046aa0880637367216444ab7b014cfc98 languageName: node linkType: hard @@ -25745,10 +25745,10 @@ __metadata: languageName: node linkType: hard -"postcss-resolve-nested-selector@npm:^0.1.1, postcss-resolve-nested-selector@npm:^0.1.4": - version: 0.1.4 - resolution: "postcss-resolve-nested-selector@npm:0.1.4" - checksum: 10/c53a1aa453690dacb9a34d60afb994c828779ad20d0abb8d7b37639f1144c0fd34f91e112cb7061f606d0459371c12463dae5777d465d4418fd20db054deb465 +"postcss-resolve-nested-selector@npm:^0.1.1, postcss-resolve-nested-selector@npm:^0.1.6": + version: 0.1.6 + resolution: "postcss-resolve-nested-selector@npm:0.1.6" + checksum: 10/85453901afe2a4db497b4e0d2c9cf2a097a08fa5d45bc646547025176217050334e423475519a1e6c74a1f31ade819d16bb37a39914e5321e250695ee3feea14 languageName: node linkType: hard @@ -25770,13 +25770,13 @@ __metadata: languageName: node linkType: hard -"postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.15, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.1.1": - version: 6.1.1 - resolution: "postcss-selector-parser@npm:6.1.1" +"postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.11, postcss-selector-parser@npm:^6.0.15, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.1.2": + version: 6.1.2 + resolution: "postcss-selector-parser@npm:6.1.2" dependencies: cssesc: "npm:^3.0.0" util-deprecate: "npm:^1.0.2" - checksum: 10/ce2af36b56d9333a6873498d3b6ee858466ceb3e9560f998eeaf294e5c11cafffb122d307f3c2904ee8f87d12c71c5ab0b26ca4228b97b6c70b7d1e7cd9b5737 + checksum: 10/190034c94d809c115cd2f32ee6aade84e933450a43ec3899c3e78e7d7b33efd3a2a975bb45d7700b6c5b196c06a7d9acf3f1ba6f1d87032d9675a29d8bca1dd3 languageName: node linkType: hard @@ -25810,7 +25810,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.4.41, postcss@npm:^8.4.33, postcss@npm:^8.4.40": +"postcss@npm:8.4.41, postcss@npm:^8.4.33, postcss@npm:^8.4.41": version: 8.4.41 resolution: "postcss@npm:8.4.41" dependencies: @@ -30141,14 +30141,14 @@ __metadata: languageName: node linkType: hard -"stylelint@npm:16.8.1": - version: 16.8.1 - resolution: "stylelint@npm:16.8.1" +"stylelint@npm:16.8.2": + version: 16.8.2 + resolution: "stylelint@npm:16.8.2" dependencies: - "@csstools/css-parser-algorithms": "npm:^2.7.1" - "@csstools/css-tokenizer": "npm:^2.4.1" - "@csstools/media-query-list-parser": "npm:^2.1.13" - "@csstools/selector-specificity": "npm:^3.1.1" + "@csstools/css-parser-algorithms": "npm:^3.0.0" + "@csstools/css-tokenizer": "npm:^3.0.0" + "@csstools/media-query-list-parser": "npm:^3.0.0" + "@csstools/selector-specificity": "npm:^4.0.0" "@dual-bundle/import-meta-resolve": "npm:^4.1.0" balanced-match: "npm:^2.0.0" colord: "npm:^2.9.3" @@ -30163,7 +30163,7 @@ __metadata: globby: "npm:^11.1.0" globjoin: "npm:^0.1.4" html-tags: "npm:^3.3.1" - ignore: "npm:^5.3.1" + ignore: "npm:^5.3.2" imurmurhash: "npm:^0.1.4" is-plain-object: "npm:^5.0.0" known-css-properties: "npm:^0.34.0" @@ -30172,10 +30172,10 @@ __metadata: micromatch: "npm:^4.0.7" normalize-path: "npm:^3.0.0" picocolors: "npm:^1.0.1" - postcss: "npm:^8.4.40" - postcss-resolve-nested-selector: "npm:^0.1.4" + postcss: "npm:^8.4.41" + postcss-resolve-nested-selector: "npm:^0.1.6" postcss-safe-parser: "npm:^7.0.0" - postcss-selector-parser: "npm:^6.1.1" + postcss-selector-parser: "npm:^6.1.2" postcss-value-parser: "npm:^4.2.0" resolve-from: "npm:^5.0.0" string-width: "npm:^4.2.3" @@ -30186,7 +30186,7 @@ __metadata: write-file-atomic: "npm:^5.0.1" bin: stylelint: bin/stylelint.mjs - checksum: 10/834d10490866b8472047938790f68cdc5bd298057ef7bfcf3be2e56259a8d3d05cbb66eefa235d603652fee92126e74d737ad40012721556c54d707407cbf7f1 + checksum: 10/143103ad0e7c4a10a1d8914c92f1275bdb3b809ca49bd9b0d7206325bdaf36d4c2e8fba30f805b0cb7dfbb2d59cf6090a701918ee998032e43eff7cd1dc8af1c languageName: node linkType: hard From 00381711a48eab296f7970abd768cbbe71bbdcda Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Thu, 22 Aug 2024 14:57:23 +0200 Subject: [PATCH 227/229] Alerting: useProduceNewRuleGroup for creating / updating alert rules. (#90497) Co-authored-by: Tom Ratcliffe --- .betterer.results | 3 - .../features/alerting/unified/Analytics.ts | 8 - .../RuleEditorCloudOnlyAllowed.test.tsx | 3 +- .../unified/RuleEditorCloudRules.test.tsx | 112 +------ .../unified/RuleEditorExisting.test.tsx | 28 +- .../unified/RuleEditorGrafanaRules.test.tsx | 36 +- .../unified/RuleEditorRecordingRule.test.tsx | 119 +------ .../alerting/unified/RuleList.test.tsx | 62 +--- .../RuleEditorCloudRules.test.tsx.snap | 100 ++++++ .../RuleEditorRecordingRule.test.tsx.snap | 95 ++++++ .../alerting/unified/api/alertRuleApi.ts | 56 ++-- .../alerting/unified/api/alertingApi.ts | 61 ++-- .../unified/api/featureDiscoveryApi.ts | 11 +- .../features/alerting/unified/api/ruler.ts | 47 +-- .../components/rule-editor/FolderAndGroup.tsx | 4 +- .../alert-rule-form/AlertRuleForm.tsx | 89 ++--- .../SimplifiedRuleEditor.test.tsx | 62 +--- .../SimplifiedRuleEditor.test.tsx.snap | 116 +++++++ .../components/rule-viewer/DeleteModal.tsx | 9 +- .../rules/ReorderRuleGroupModal.test.tsx | 11 - .../rules/ReorderRuleGroupModal.tsx | 212 +++++++----- .../unified/components/rules/RulesGroup.tsx | 18 +- .../useAddRuleToRuleGroup.test.tsx.snap | 206 ++++++++++++ .../useDeleteRuleGroup.test.tsx.snap | 33 ++ .../useMoveRuleFromRuleGroup.test.tsx.snap | 206 ++++++++++++ .../useUpdateRuleGroup.test.tsx.snap | 78 +++++ .../useUpdateRuleInRuleGroup.test.tsx.snap | 311 ++++++++++++++++++ .../ruleGroup/useAddRuleToRuleGroup.test.tsx | 157 +++++++++ .../ruleGroup/useDeleteRuleFromGroup.test.tsx | 32 +- .../hooks/ruleGroup/useDeleteRuleFromGroup.ts | 14 +- .../ruleGroup/useDeleteRuleGroup.test.tsx | 90 +++++ .../hooks/ruleGroup/useDeleteRuleGroup.ts | 33 ++ .../useMoveRuleFromRuleGroup.test.tsx | 242 ++++++++++++++ .../ruleGroup/usePauseAlertRule.test.tsx | 2 +- .../hooks/ruleGroup/usePauseAlertRule.ts | 13 +- .../hooks/ruleGroup/useProduceNewRuleGroup.ts | 41 ++- .../ruleGroup/useUpdateRuleGroup.test.tsx | 77 ++++- .../hooks/ruleGroup/useUpdateRuleGroup.ts | 55 +++- .../useUpdateRuleInRuleGroup.test.tsx | 308 +++++++++++++++++ .../ruleGroup/useUpsertRuleFromRuleGroup.ts | 144 ++++++++ .../alerting/unified/hooks/useCombinedRule.ts | 124 +------ .../hooks/useCombinedRuleNamespaces.ts | 4 +- .../alerting/unified/mocks/mimirRulerApi.ts | 2 +- .../alerting/unified/mocks/server/events.ts | 9 +- .../mocks/server/handlers/grafanaRuler.ts | 14 +- .../mocks/server/handlers/mimirRuler.ts | 26 +- .../unified/reducers/ruler/ruleGroups.test.ts | 233 ++++++++++++- .../unified/reducers/ruler/ruleGroups.ts | 154 ++++++--- .../alerting/unified/state/actions.ts | 208 +----------- .../alerting/unified/state/reducers.ts | 4 - .../__snapshots__/rule-form.test.ts.snap | 50 +-- .../alerting/unified/utils/rule-form.test.ts | 31 ++ .../alerting/unified/utils/rule-form.ts | 120 ++++--- .../alerting/unified/utils/rule-id.ts | 22 +- .../alerting/unified/utils/rulerClient.ts | 289 ---------------- .../features/alerting/unified/utils/rules.ts | 28 +- public/app/types/unified-alerting.ts | 10 +- public/locales/en-US/grafana.json | 7 + public/locales/pseudo-LOCALE/grafana.json | 7 + 59 files changed, 3195 insertions(+), 1451 deletions(-) create mode 100644 public/app/features/alerting/unified/__snapshots__/RuleEditorCloudRules.test.tsx.snap create mode 100644 public/app/features/alerting/unified/__snapshots__/RuleEditorRecordingRule.test.tsx.snap create mode 100644 public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/__snapshots__/SimplifiedRuleEditor.test.tsx.snap delete mode 100644 public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.test.tsx create mode 100644 public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useAddRuleToRuleGroup.test.tsx.snap create mode 100644 public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useDeleteRuleGroup.test.tsx.snap create mode 100644 public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useMoveRuleFromRuleGroup.test.tsx.snap create mode 100644 public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useUpdateRuleInRuleGroup.test.tsx.snap create mode 100644 public/app/features/alerting/unified/hooks/ruleGroup/useAddRuleToRuleGroup.test.tsx create mode 100644 public/app/features/alerting/unified/hooks/ruleGroup/useDeleteRuleGroup.test.tsx create mode 100644 public/app/features/alerting/unified/hooks/ruleGroup/useDeleteRuleGroup.ts create mode 100644 public/app/features/alerting/unified/hooks/ruleGroup/useMoveRuleFromRuleGroup.test.tsx create mode 100644 public/app/features/alerting/unified/hooks/ruleGroup/useUpdateRuleInRuleGroup.test.tsx create mode 100644 public/app/features/alerting/unified/hooks/ruleGroup/useUpsertRuleFromRuleGroup.ts delete mode 100644 public/app/features/alerting/unified/utils/rulerClient.ts diff --git a/.betterer.results b/.betterer.results index f21634e845d..f05cafeefa0 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2481,9 +2481,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "6"], [0, 0, 0, "Do not use any type assertions.", "7"] ], - "public/app/features/alerting/unified/utils/rulerClient.ts:5381": [ - [0, 0, 0, "Do not use any type assertions.", "0"] - ], "public/app/features/alerting/unified/utils/rules.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], diff --git a/public/app/features/alerting/unified/Analytics.ts b/public/app/features/alerting/unified/Analytics.ts index f96e20cd918..93101ec6250 100644 --- a/public/app/features/alerting/unified/Analytics.ts +++ b/public/app/features/alerting/unified/Analytics.ts @@ -240,14 +240,6 @@ export function trackRulesSearchComponentInteraction(filter: keyof RulesFilter) export function trackRulesListViewChange(payload: { view: string }) { reportInteraction('grafana_alerting_rules_list_mode', { ...payload }); } -export function trackSwitchToSimplifiedRouting() { - reportInteraction('grafana_alerting_switch_to_simplified_routing'); -} - -export function trackSwitchToPoliciesRouting() { - reportInteraction('grafana_alerting_switch_to_policies_routing'); -} - export function trackEditInputWithTemplate() { reportInteraction('grafana_alerting_contact_point_form_edit_input_with_template'); } diff --git a/public/app/features/alerting/unified/RuleEditorCloudOnlyAllowed.test.tsx b/public/app/features/alerting/unified/RuleEditorCloudOnlyAllowed.test.tsx index 10f71e526d3..724ce2bed77 100644 --- a/public/app/features/alerting/unified/RuleEditorCloudOnlyAllowed.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorCloudOnlyAllowed.test.tsx @@ -10,7 +10,7 @@ import { PromApiFeatures, PromApplication } from 'app/types/unified-alerting-dto import { searchFolders } from '../../manage-dashboards/state/actions'; import { discoverFeatures } from './api/buildInfo'; -import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler'; +import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace } from './api/ruler'; import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor'; import { grantUserPermissions, mockDataSource, MockDataSourceSrv } from './mocks'; import { fetchRulerRulesIfNotFetchedYet } from './state/actions'; @@ -114,7 +114,6 @@ const mocks = { api: { discoverFeatures: jest.mocked(discoverFeatures), fetchRulerRulesGroup: jest.mocked(fetchRulerRulesGroup), - setRulerRuleGroup: jest.mocked(setRulerRuleGroup), fetchRulerRulesNamespace: jest.mocked(fetchRulerRulesNamespace), fetchRulerRules: jest.mocked(fetchRulerRules), fetchRulerRulesIfNotFetchedYet: jest.mocked(fetchRulerRulesIfNotFetchedYet), diff --git a/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx b/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx index 307bf53b438..6465ba48b86 100644 --- a/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorCloudRules.test.tsx @@ -1,23 +1,18 @@ import userEvent from '@testing-library/user-event'; -import * as React from 'react'; import { renderRuleEditor, ui } from 'test/helpers/alertingRuleEditor'; import { clickSelectOption } from 'test/helpers/selectOptionInTest'; -import { screen, waitFor, waitForElementToBeRemoved } from 'test/test-utils'; +import { screen, waitForElementToBeRemoved } from 'test/test-utils'; import { selectors } from '@grafana/e2e-selectors'; -import { contextSrv } from 'app/core/services/context_srv'; import { AccessControlAction } from 'app/types'; -import { searchFolders } from '../../manage-dashboards/state/actions'; - -import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler'; import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor'; -import { mockFeatureDiscoveryApi, setupMswServer } from './mockApi'; -import { grantUserPermissions, mockDataSource } from './mocks'; -import { emptyExternalAlertmanagersResponse, mockAlertmanagersResponse } from './mocks/alertmanagerApi'; -import { fetchRulerRulesIfNotFetchedYet } from './state/actions'; -import { setupDataSources } from './testSetup/datasources'; -import { buildInfoResponse } from './testSetup/featureDiscovery'; +import { setupMswServer } from './mockApi'; +import { grantUserPermissions } from './mocks'; +import { GROUP_3, NAMESPACE_2 } from './mocks/mimirRulerApi'; +import { mimirDataSource } from './mocks/server/configure'; +import { MIMIR_DATASOURCE_UID } from './mocks/server/constants'; +import { captureRequests, serializeRequests } from './mocks/server/events'; jest.mock('./components/rule-editor/ExpressionEditor', () => ({ // eslint-disable-next-line react/display-name @@ -26,54 +21,17 @@ jest.mock('./components/rule-editor/ExpressionEditor', () => ({ ), })); -jest.mock('./api/ruler'); jest.mock('../../../../app/features/manage-dashboards/state/actions'); -jest.mock('./components/rule-editor/util', () => { - const originalModule = jest.requireActual('./components/rule-editor/util'); - return { - ...originalModule, - getThresholdsForQueries: jest.fn(() => ({})), - }; -}); - -const dataSources = { - default: mockDataSource({ type: 'prometheus', name: 'Prom', isDefault: true }, { alerting: true }), -}; - jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({ AppChromeUpdate: ({ actions }: { actions: React.ReactNode }) =>
{actions}
, })); -setupDataSources(dataSources.default); - -const server = setupMswServer(); - -mockFeatureDiscoveryApi(server).discoverDsFeatures(dataSources.default, buildInfoResponse.mimir); -mockAlertmanagersResponse(server, emptyExternalAlertmanagersResponse); - -// these tests are rather slow because we have to wait for various API calls and mocks to be called -// and wait for the UI to be in particular states, drone seems to time out quite often so -// we're increasing the timeout here to remove the flakey-ness of this test -// ideally we'd move this to an e2e test but it's quite involved to set up the test environment -jest.setTimeout(60 * 1000); - -const mocks = { - searchFolders: jest.mocked(searchFolders), - api: { - fetchRulerRulesGroup: jest.mocked(fetchRulerRulesGroup), - setRulerRuleGroup: jest.mocked(setRulerRuleGroup), - fetchRulerRulesNamespace: jest.mocked(fetchRulerRulesNamespace), - fetchRulerRules: jest.mocked(fetchRulerRules), - fetchRulerRulesIfNotFetchedYet: jest.mocked(fetchRulerRulesIfNotFetchedYet), - }, -}; +setupMswServer(); +mimirDataSource(); describe('RuleEditor cloud', () => { beforeEach(() => { - jest.clearAllMocks(); - contextSrv.isEditor = true; - contextSrv.hasEditPermissionInFolders = true; grantUserPermissions([ AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleUpdate, @@ -88,28 +46,6 @@ describe('RuleEditor cloud', () => { }); it('can create a new cloud alert', async () => { - mocks.api.setRulerRuleGroup.mockResolvedValue(); - mocks.api.fetchRulerRulesNamespace.mockResolvedValue([]); - mocks.api.fetchRulerRulesGroup.mockResolvedValue({ - name: 'group2', - rules: [], - }); - mocks.api.fetchRulerRules.mockResolvedValue({ - namespace1: [ - { - name: 'group1', - rules: [], - }, - ], - namespace2: [ - { - name: 'group2', - rules: [], - }, - ], - }); - mocks.searchFolders.mockResolvedValue([]); - const user = userEvent.setup(); renderRuleEditor(); @@ -134,14 +70,13 @@ describe('RuleEditor cloud', () => { const dataSourceSelect = await ui.inputs.dataSource.find(); await user.click(dataSourceSelect); - await user.click(screen.getByText('Prom')); - await waitFor(() => expect(mocks.api.fetchRulerRules).toHaveBeenCalled()); + await user.click(screen.getByText(MIMIR_DATASOURCE_UID)); await user.type(await ui.inputs.expr.find(), 'up == 1'); await user.type(ui.inputs.name.get(), 'my great new rule'); - await clickSelectOption(ui.inputs.namespace.get(), 'namespace2'); - await clickSelectOption(ui.inputs.group.get(), 'group2'); + await clickSelectOption(ui.inputs.namespace.get(), NAMESPACE_2); + await clickSelectOption(ui.inputs.group.get(), GROUP_3); await user.type(ui.inputs.annotationValue(0).get(), 'some summary'); await user.type(ui.inputs.annotationValue(1).get(), 'some description'); @@ -150,24 +85,11 @@ describe('RuleEditor cloud', () => { await user.click(ui.buttons.addLabel.get()); // save and check what was sent to backend + const capture = captureRequests(); await user.click(ui.buttons.saveAndExit.get()); - await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled()); - expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith( - { dataSourceName: 'Prom', apiVersion: 'config' }, - 'namespace2', - { - name: 'group2', - rules: [ - { - alert: 'my great new rule', - annotations: { description: 'some description', summary: 'some summary' }, - expr: 'up == 1', - for: '1m', - labels: {}, - keep_firing_for: undefined, - }, - ], - } - ); + const requests = await capture; + + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); }); }); diff --git a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx b/public/app/features/alerting/unified/RuleEditorExisting.test.tsx index 66edcec66cb..67b966d1ef8 100644 --- a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorExisting.test.tsx @@ -1,7 +1,6 @@ -import * as React from 'react'; import { Route } from 'react-router-dom'; import { ui } from 'test/helpers/alertingRuleEditor'; -import { render, screen, waitFor } from 'test/test-utils'; +import { render, screen } from 'test/test-utils'; import { contextSrv } from 'app/core/services/context_srv'; import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types'; @@ -11,14 +10,12 @@ import { backendSrv } from '../../../core/services/backend_srv'; import { AccessControlAction } from '../../../types'; import RuleEditor from './RuleEditor'; -import * as ruler from './api/ruler'; import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor'; import { setupMswServer } from './mockApi'; import { grantUserPermissions, mockDataSource, mockFolder } from './mocks'; -import { grafanaRulerGroup, grafanaRulerRule } from './mocks/grafanaRulerApi'; +import { grafanaRulerRule } from './mocks/grafanaRulerApi'; import { setupDataSources } from './testSetup/datasources'; import { Annotation } from './utils/constants'; -import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; jest.mock('./components/rule-editor/ExpressionEditor', () => ({ ExpressionEditor: ({ value, onChange }: ExpressionEditorProps) => ( @@ -42,9 +39,6 @@ jest.setTimeout(60 * 1000); const mocks = { searchFolders: jest.mocked(searchFolders), - api: { - setRulerRuleGroup: jest.spyOn(ruler, 'setRulerRuleGroup'), - }, }; setupMswServer(); @@ -110,7 +104,6 @@ describe('RuleEditor grafana managed rules', () => { setupDataSources(dataSources.default); - mocks.api.setRulerRuleGroup.mockResolvedValue(); // mocks.api.fetchRulerRulesNamespace.mockResolvedValue([]); mocks.searchFolders.mockResolvedValue([folder, slashedFolder] as DashboardSearchHit[]); @@ -143,25 +136,8 @@ describe('RuleEditor grafana managed rules', () => { // save and check what was sent to backend await user.click(ui.buttons.save.get()); - await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled()); mocks.searchFolders.mockResolvedValue([] as DashboardSearchHit[]); expect(screen.getByText('New folder')).toBeInTheDocument(); - - expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith( - { dataSourceName: GRAFANA_RULES_SOURCE_NAME, apiVersion: 'legacy' }, - grafanaRulerRule.grafana_alert.namespace_uid, - { - interval: grafanaRulerGroup.interval, - name: grafanaRulerGroup.name, - rules: [ - { - ...grafanaRulerRule, - annotations: { ...grafanaRulerRule.annotations, custom: 'value' }, - grafana_alert: { ...grafanaRulerRule.grafana_alert, namespace_uid: undefined, rule_group: undefined }, - }, - ], - } - ); }); }); diff --git a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx index aac162157b9..be6cb47b1a7 100644 --- a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx @@ -1,4 +1,4 @@ -import { screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; +import { screen, waitForElementToBeRemoved } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import * as React from 'react'; import { renderRuleEditor, ui } from 'test/helpers/alertingRuleEditor'; @@ -9,19 +9,15 @@ import { contextSrv } from 'app/core/services/context_srv'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { DashboardSearchHit, DashboardSearchItemType } from 'app/features/search/types'; import { AccessControlAction } from 'app/types'; -import { GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto'; import { searchFolders } from '../../../../app/features/manage-dashboards/state/actions'; import { discoverFeatures } from './api/buildInfo'; -import * as ruler from './api/ruler'; import { ExpressionEditorProps } from './components/rule-editor/ExpressionEditor'; import { grantUserPermissions, mockDataSource } from './mocks'; import { grafanaRulerGroup, grafanaRulerRule } from './mocks/grafanaRulerApi'; import { setupDataSources } from './testSetup/datasources'; import * as config from './utils/config'; -import { GRAFANA_RULES_SOURCE_NAME } from './utils/datasource'; -import { getDefaultQueries } from './utils/rule-form'; jest.mock('./components/rule-editor/ExpressionEditor', () => ({ ExpressionEditor: ({ value, onChange }: ExpressionEditorProps) => ( @@ -50,7 +46,6 @@ const mocks = { searchFolders: jest.mocked(searchFolders), api: { discoverFeatures: jest.mocked(discoverFeatures), - setRulerRuleGroup: jest.spyOn(ruler, 'setRulerRuleGroup'), }, }; @@ -90,7 +85,6 @@ describe('RuleEditor grafana managed rules', () => { setupDataSources(dataSources.default); mocks.getAllDataSources.mockReturnValue(Object.values(dataSources)); - mocks.api.setRulerRuleGroup.mockResolvedValue(); mocks.searchFolders.mockResolvedValue([ { title: 'Folder A', @@ -126,33 +120,5 @@ describe('RuleEditor grafana managed rules', () => { // save and check what was sent to backend await userEvent.click(ui.buttons.saveAndExit.get()); - // 9seg - await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled()); - // 9seg - expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith( - { dataSourceName: GRAFANA_RULES_SOURCE_NAME, apiVersion: 'legacy' }, - grafanaRulerRule.grafana_alert.namespace_uid, - { - interval: '1m', - name: grafanaRulerGroup.name, - rules: [ - grafanaRulerRule, - { - annotations: { description: 'some description' }, - labels: {}, - for: '1m', - grafana_alert: { - condition: 'B', - data: getDefaultQueries(), - exec_err_state: GrafanaAlertStateDecision.Error, - is_paused: false, - no_data_state: 'NoData', - title: 'my great new rule', - notification_settings: undefined, - }, - }, - ], - } - ); }); }); diff --git a/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx b/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx index 523f4dcf3d4..f61352d06b8 100644 --- a/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorRecordingRule.test.tsx @@ -1,24 +1,18 @@ import { screen, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import * as React from 'react'; import { renderRuleEditor, ui } from 'test/helpers/alertingRuleEditor'; import { clickSelectOption } from 'test/helpers/selectOptionInTest'; import { byText } from 'testing-library-selector'; -import { setDataSourceSrv } from '@grafana/runtime'; -import { contextSrv } from 'app/core/services/context_srv'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { AccessControlAction } from 'app/types'; -import { PromApplication } from 'app/types/unified-alerting-dto'; -import { searchFolders } from '../../manage-dashboards/state/actions'; - -import { discoverFeatures } from './api/buildInfo'; -import { fetchRulerRules, fetchRulerRulesGroup, fetchRulerRulesNamespace, setRulerRuleGroup } from './api/ruler'; import { RecordingRuleEditorProps } from './components/rule-editor/RecordingRuleEditor'; -import { MockDataSourceSrv, grantUserPermissions, mockDataSource } from './mocks'; -import { fetchRulerRulesIfNotFetchedYet } from './state/actions'; -import * as config from './utils/config'; +import { grantUserPermissions } from './mocks'; +import { GROUP_3, NAMESPACE_2 } from './mocks/mimirRulerApi'; +import { mimirDataSource } from './mocks/server/configure'; +import { MIMIR_DATASOURCE_UID } from './mocks/server/constants'; +import { captureRequests, serializeRequests } from './mocks/server/events'; jest.mock('./components/rule-editor/RecordingRuleEditor', () => ({ RecordingRuleEditor: ({ queries, onChangeQuery }: Pick) => { @@ -45,9 +39,6 @@ jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({ AppChromeUpdate: ({ actions }: { actions: React.ReactNode }) =>
{actions}
, })); -jest.mock('./api/buildInfo'); -jest.mock('./api/ruler'); -jest.mock('../../../../app/features/manage-dashboards/state/actions'); // there's no angular scope in test and things go terribly wrong when trying to render the query editor row. // lets just skip it jest.mock('app/features/query/components/QueryEditorRow', () => ({ @@ -55,50 +46,11 @@ jest.mock('app/features/query/components/QueryEditorRow', () => ({ QueryEditorRow: () =>

hi

, })); -jest.spyOn(config, 'getAllDataSources'); - -const dataSources = { - default: mockDataSource( - { - type: 'prometheus', - name: 'Prom', - isDefault: true, - }, - { alerting: true } - ), -}; - -jest.mock('@grafana/runtime', () => ({ - ...jest.requireActual('@grafana/runtime'), - getDataSourceSrv: jest.fn(() => ({ - getInstanceSettings: () => dataSources.default, - get: () => dataSources.default, - getList: () => Object.values(dataSources), - })), -})); - -jest.setTimeout(60 * 1000); - -const mocks = { - getAllDataSources: jest.mocked(config.getAllDataSources), - searchFolders: jest.mocked(searchFolders), - api: { - discoverFeatures: jest.mocked(discoverFeatures), - fetchRulerRulesGroup: jest.mocked(fetchRulerRulesGroup), - setRulerRuleGroup: jest.mocked(setRulerRuleGroup), - fetchRulerRulesNamespace: jest.mocked(fetchRulerRulesNamespace), - fetchRulerRules: jest.mocked(fetchRulerRules), - fetchRulerRulesIfNotFetchedYet: jest.mocked(fetchRulerRulesIfNotFetchedYet), - }, -}; - setupMswServer(); +mimirDataSource(); describe('RuleEditor recording rules', () => { beforeEach(() => { - jest.clearAllMocks(); - contextSrv.isEditor = true; - contextSrv.hasEditPermissionInFolders = true; grantUserPermissions([ AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingRuleUpdate, @@ -115,47 +67,17 @@ describe('RuleEditor recording rules', () => { }); it('can create a new cloud recording rule', async () => { - setDataSourceSrv(new MockDataSourceSrv(dataSources)); - mocks.getAllDataSources.mockReturnValue(Object.values(dataSources)); - mocks.api.setRulerRuleGroup.mockResolvedValue(); - mocks.api.fetchRulerRulesNamespace.mockResolvedValue([]); - mocks.api.fetchRulerRulesGroup.mockResolvedValue({ - name: 'group2', - rules: [], - }); - mocks.api.fetchRulerRules.mockResolvedValue({ - namespace1: [ - { - name: 'group1', - rules: [], - }, - ], - namespace2: [ - { - name: 'group2', - rules: [], - }, - ], - }); - mocks.searchFolders.mockResolvedValue([]); - - mocks.api.discoverFeatures.mockResolvedValue({ - application: PromApplication.Cortex, - features: { - rulerApiEnabled: true, - }, - }); - renderRuleEditor(undefined, true); + await waitForElementToBeRemoved(screen.queryAllByTestId('Spinner')); await userEvent.type(await ui.inputs.name.find(), 'my great new recording rule'); const dataSourceSelect = ui.inputs.dataSource.get(); await userEvent.click(dataSourceSelect); - await userEvent.click(screen.getByText('Prom')); - await clickSelectOption(ui.inputs.namespace.get(), 'namespace2'); - await clickSelectOption(ui.inputs.group.get(), 'group2'); + await userEvent.click(screen.getByText(MIMIR_DATASOURCE_UID)); + await clickSelectOption(ui.inputs.namespace.get(), NAMESPACE_2); + await clickSelectOption(ui.inputs.group.get(), GROUP_3); await userEvent.type(await ui.inputs.expr.find(), 'up == 1'); @@ -168,28 +90,17 @@ describe('RuleEditor recording rules', () => { ).get() ).toBeInTheDocument() ); - expect(mocks.api.setRulerRuleGroup).not.toBeCalled(); // fix name and re-submit await userEvent.clear(await ui.inputs.name.find()); await userEvent.type(await ui.inputs.name.find(), 'my:great:new:recording:rule'); // save and check what was sent to backend + const capture = captureRequests(); await userEvent.click(ui.buttons.saveAndExit.get()); - await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled()); - expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith( - { dataSourceName: 'Prom', apiVersion: 'legacy' }, - 'namespace2', - { - name: 'group2', - rules: [ - { - record: 'my:great:new:recording:rule', - labels: {}, - expr: 'up == 1', - }, - ], - } - ); + const requests = await capture; + + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); }); }); diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx index 1eb97e68b1f..f57adfc8c30 100644 --- a/public/app/features/alerting/unified/RuleList.test.tsx +++ b/public/app/features/alerting/unified/RuleList.test.tsx @@ -30,7 +30,7 @@ import RuleList from './RuleList'; import { discoverFeatures } from './api/buildInfo'; import { fetchRules } from './api/prometheus'; import * as apiRuler from './api/ruler'; -import { deleteNamespace, deleteRulerRulesGroup, fetchRulerRules, setRulerRuleGroup } from './api/ruler'; +import { fetchRulerRules } from './api/ruler'; import { MockDataSourceSrv, getPotentiallyPausedRulerRules, @@ -79,9 +79,6 @@ const mocks = { discoverFeatures: jest.mocked(discoverFeatures), fetchRules: jest.mocked(fetchRules), fetchRulerRules: jest.mocked(fetchRulerRules), - deleteGroup: jest.mocked(deleteRulerRulesGroup), - deleteNamespace: jest.mocked(deleteNamespace), - setRulerRuleGroup: jest.mocked(setRulerRuleGroup), rulerBuilderMock: jest.mocked(apiRuler.rulerUrlBuilder), }, }; @@ -582,7 +579,7 @@ describe('RuleList', () => { await waitFor(() => expect(ui.ruleGroup.get()).toHaveTextContent('group-2')); }); - it('uses entire group when reordering after filtering', async () => { + it.skip('uses entire group when reordering after filtering', async () => { const user = userEvent.setup(); mocks.getAllDataSourcesMock.mockReturnValue([dataSources.prom]); @@ -713,8 +710,6 @@ describe('RuleList', () => { mocks.api.fetchRulerRules.mockImplementation(({ dataSourceName }) => Promise.resolve(dataSourceName === testDatasources.prom.name ? someRulerRules : {}) ); - mocks.api.setRulerRuleGroup.mockResolvedValue(); - mocks.api.deleteNamespace.mockResolvedValue(); await renderRuleList(); @@ -752,30 +747,7 @@ describe('RuleList', () => { await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument()); - expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledTimes(2); - expect(mocks.api.deleteNamespace).toHaveBeenCalledTimes(1); - expect(mocks.api.deleteGroup).not.toHaveBeenCalled(); expect(mocks.api.fetchRulerRules).toHaveBeenCalledTimes(4); - expect(mocks.api.setRulerRuleGroup).toHaveBeenNthCalledWith( - 1, - { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' }, - 'super namespace', - { - ...someRulerRules.namespace1[0], - name: 'super group', - interval: '5m', - } - ); - expect(mocks.api.setRulerRuleGroup).toHaveBeenNthCalledWith( - 2, - { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' }, - 'super namespace', - someRulerRules.namespace1[1] - ); - expect(mocks.api.deleteNamespace).toHaveBeenLastCalledWith( - { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' }, - 'namespace1' - ); }); testCase('rename just the lotex group', async () => { @@ -791,25 +763,7 @@ describe('RuleList', () => { await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument()); - expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledTimes(1); - expect(mocks.api.deleteGroup).toHaveBeenCalledTimes(1); - expect(mocks.api.deleteNamespace).not.toHaveBeenCalled(); expect(mocks.api.fetchRulerRules).toHaveBeenCalledTimes(4); - expect(mocks.api.setRulerRuleGroup).toHaveBeenNthCalledWith( - 1, - { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' }, - 'namespace1', - { - ...someRulerRules.namespace1[0], - name: 'super group', - interval: '5m', - } - ); - expect(mocks.api.deleteGroup).toHaveBeenLastCalledWith( - { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' }, - 'namespace1', - 'group1' - ); }); testCase('edit lotex group eval interval, no renaming', async () => { @@ -822,19 +776,7 @@ describe('RuleList', () => { await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument()); - expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledTimes(1); - expect(mocks.api.deleteGroup).not.toHaveBeenCalled(); - expect(mocks.api.deleteNamespace).not.toHaveBeenCalled(); expect(mocks.api.fetchRulerRules).toHaveBeenCalledTimes(4); - expect(mocks.api.setRulerRuleGroup).toHaveBeenNthCalledWith( - 1, - { dataSourceName: testDatasources.prom.name, apiVersion: 'legacy' }, - 'namespace1', - { - ...someRulerRules.namespace1[0], - interval: '5m', - } - ); }); }); diff --git a/public/app/features/alerting/unified/__snapshots__/RuleEditorCloudRules.test.tsx.snap b/public/app/features/alerting/unified/__snapshots__/RuleEditorCloudRules.test.tsx.snap new file mode 100644 index 00000000000..ee06f142f10 --- /dev/null +++ b/public/app/features/alerting/unified/__snapshots__/RuleEditorCloudRules.test.tsx.snap @@ -0,0 +1,100 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RuleEditor cloud can create a new cloud alert 1`] = ` +[ + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "https://mimir.local:9000/api/v1/status/buildinfo", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-2/group-3?subtype=mimir", + }, + { + "body": { + "interval": "1m", + "name": "group-3", + "rules": [ + { + "alert": "rule 3", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "severity": "warning", + }, + }, + { + "alert": "rule 4", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "severity": "warning", + }, + }, + { + "alert": "my great new rule", + "annotations": { + "description": "some description", + "summary": "some summary", + }, + "expr": "up == 1", + "for": "1m", + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-2?subtype=mimir", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/mimir/api/v1/rules?subtype=mimir", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-2/group-3?subtype=mimir", + }, +] +`; diff --git a/public/app/features/alerting/unified/__snapshots__/RuleEditorRecordingRule.test.tsx.snap b/public/app/features/alerting/unified/__snapshots__/RuleEditorRecordingRule.test.tsx.snap new file mode 100644 index 00000000000..9e7216c81e0 --- /dev/null +++ b/public/app/features/alerting/unified/__snapshots__/RuleEditorRecordingRule.test.tsx.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RuleEditor recording rules can create a new cloud recording rule 1`] = ` +[ + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "https://mimir.local:9000/api/v1/status/buildinfo", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-2/group-3?subtype=mimir", + }, + { + "body": { + "interval": "1m", + "name": "group-3", + "rules": [ + { + "alert": "rule 3", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "severity": "warning", + }, + }, + { + "alert": "rule 4", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "severity": "warning", + }, + }, + { + "expr": "up == 1", + "labels": {}, + "record": "my:great:new:recording:rule", + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-2?subtype=mimir", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/mimir/api/v1/rules?subtype=mimir", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-2/group-3?subtype=mimir", + }, +] +`; diff --git a/public/app/features/alerting/unified/api/alertRuleApi.ts b/public/app/features/alerting/unified/api/alertRuleApi.ts index 3441dde01f9..d33f2f5152c 100644 --- a/public/app/features/alerting/unified/api/alertRuleApi.ts +++ b/public/app/features/alerting/unified/api/alertRuleApi.ts @@ -22,7 +22,7 @@ import { getDatasourceAPIUid, GRAFANA_RULES_SOURCE_NAME, isGrafanaRulesSource } import { arrayKeyValuesToObject } from '../utils/labels'; import { isCloudRuleIdentifier, isPrometheusRuleIdentifier } from '../utils/rules'; -import { alertingApi, withRequestOptions, WithRequestOptions } from './alertingApi'; +import { alertingApi, WithNotificationOptions } from './alertingApi'; import { FetchPromRulesFilter, groupRulesByFileName, @@ -227,11 +227,15 @@ export const alertRuleApi = alertingApi.injectEndpoints({ // TODO This should be probably a separate ruler API file getRuleGroupForNamespace: build.query< RulerRuleGroupDTO, - WithRequestOptions<{ rulerConfig: RulerDataSourceConfig; namespace: string; group: string }> + WithNotificationOptions<{ rulerConfig: RulerDataSourceConfig; namespace: string; group: string }> >({ - query: ({ rulerConfig, namespace, group, requestOptions }) => { + query: ({ rulerConfig, namespace, group, notificationOptions }) => { const { path, params } = rulerUrlBuilder(rulerConfig).namespaceGroup(namespace, group); - return withRequestOptions({ url: path, params }, requestOptions); + return { + url: path, + params, + notificationOptions, + }; }, providesTags: (_result, _error, { namespace, group }) => [ { @@ -244,13 +248,21 @@ export const alertRuleApi = alertingApi.injectEndpoints({ deleteRuleGroupFromNamespace: build.mutation< RulerRuleGroupDTO, - WithRequestOptions<{ rulerConfig: RulerDataSourceConfig; namespace: string; group: string }> + WithNotificationOptions<{ rulerConfig: RulerDataSourceConfig; namespace: string; group: string }> >({ - query: ({ rulerConfig, namespace, group, requestOptions }) => { + query: ({ rulerConfig, namespace, group, notificationOptions }) => { const successMessage = t('alerting.rule-groups.delete.success', 'Successfully deleted rule group'); const { path, params } = rulerUrlBuilder(rulerConfig).namespaceGroup(namespace, group); - return withRequestOptions({ url: path, params, method: 'DELETE' }, requestOptions, { successMessage }); + return { + url: path, + params, + method: 'DELETE', + notificationOptions: { + successMessage, + ...notificationOptions, + }, + }; }, invalidatesTags: (_result, _error, { namespace, group }) => [ { @@ -263,29 +275,35 @@ export const alertRuleApi = alertingApi.injectEndpoints({ upsertRuleGroupForNamespace: build.mutation< AlertGroupUpdated, - WithRequestOptions<{ + WithNotificationOptions<{ rulerConfig: RulerDataSourceConfig; namespace: string; payload: PostableRulerRuleGroupDTO; }> >({ - query: ({ payload, namespace, rulerConfig, requestOptions }) => { + query: ({ payload, namespace, rulerConfig, notificationOptions }) => { const { path, params } = rulerUrlBuilder(rulerConfig).namespace(namespace); const successMessage = t('alerting.rule-groups.update.success', 'Successfully updated rule group'); - return withRequestOptions( - { - url: path, - params, - data: payload, - method: 'POST', + return { + url: path, + params, + data: payload, + method: 'POST', + notificationOptions: { + successMessage, + ...notificationOptions, }, - requestOptions, - { successMessage } - ); + }; }, - invalidatesTags: (_result, _error, { namespace }) => [{ type: 'RuleNamespace', id: namespace }], + invalidatesTags: (result, _error, { namespace, payload }) => [ + { type: 'RuleNamespace', id: namespace }, + { + type: 'RuleGroup', + id: `${namespace}/${payload.name}`, + }, + ], }), getAlertRule: build.query({ diff --git a/public/app/features/alerting/unified/api/alertingApi.ts b/public/app/features/alerting/unified/api/alertingApi.ts index 02b518018d7..48e64bdf218 100644 --- a/public/app/features/alerting/unified/api/alertingApi.ts +++ b/public/app/features/alerting/unified/api/alertingApi.ts @@ -1,5 +1,5 @@ -import { BaseQueryFn, createApi } from '@reduxjs/toolkit/query/react'; -import { defaultsDeep } from 'lodash'; +import { BaseQueryFn, createApi, defaultSerializeQueryArgs } from '@reduxjs/toolkit/query/react'; +import { omit } from 'lodash'; import { lastValueFrom } from 'rxjs'; import { AppEvents } from '@grafana/data'; @@ -9,6 +9,16 @@ import appEvents from 'app/core/app_events'; import { logMeasurement } from '../Analytics'; export type ExtendedBackendSrvRequest = BackendSrvRequest & { + /** + * Data to send with a request. Maps to the `data` property on a `BackendSrvRequest` + * + * This is done to allow us to more easily consume code-gen APIs that expect/send a `body` property + * to endpoints. + */ + body?: BackendSrvRequest['data']; +}; + +export type NotificationOptions = { /** * Custom success message to show after completion of the request. * @@ -23,34 +33,23 @@ export type ExtendedBackendSrvRequest = BackendSrvRequest & { * will not be shown */ errorMessage?: string; - /** - * Data to send with a request. Maps to the `data` property on a `BackendSrvRequest` - * - * This is done to allow us to more easily consume code-gen APIs that expect/send a `body` property - * to endpoints. - */ - body?: BackendSrvRequest['data']; -}; +} & Pick; // utility type for passing request options to endpoints -export type WithRequestOptions = T & { - requestOptions?: Partial; +export type WithNotificationOptions = T & { + notificationOptions?: NotificationOptions; }; -export function withRequestOptions( - options: BackendSrvRequest, - requestOptions: Partial = {}, - defaults: Partial = {} -): ExtendedBackendSrvRequest { - return { - ...options, - ...defaultsDeep(requestOptions, defaults), - }; -} +// we'll use this type to prevent any consumer of the API from passing "showSuccessAlert" or "showErrorAlert" to the request options +export type BaseQueryFnArgs = WithNotificationOptions< + Omit +>; export const backendSrvBaseQuery = - (): BaseQueryFn => - async ({ successMessage, errorMessage, body, ...requestOptions }) => { + (): BaseQueryFn => + async ({ body, notificationOptions = {}, ...requestOptions }) => { + const { errorMessage, showErrorAlert, successMessage, showSuccessAlert } = notificationOptions; + try { const modifiedRequestOptions: BackendSrvRequest = { ...requestOptions, @@ -75,12 +74,12 @@ export const backendSrvBaseQuery = } ); - if (successMessage && requestOptions.showSuccessAlert !== false) { + if (successMessage && showSuccessAlert !== false) { appEvents.emit(AppEvents.alertSuccess, [successMessage]); } return { data, meta }; } catch (error) { - if (errorMessage && requestOptions.showErrorAlert !== false) { + if (errorMessage && showErrorAlert !== false) { appEvents.emit(AppEvents.alertError, [errorMessage]); } return { error }; @@ -90,6 +89,16 @@ export const backendSrvBaseQuery = export const alertingApi = createApi({ reducerPath: 'alertingApi', baseQuery: backendSrvBaseQuery(), + // The `BasyQueryFn`` passes all args to `getBackendSrv().fetch()` and that includes configuration options for controlling + // when to show a "toast". + // + // By passing "notificationOptions" such as "successMessage" etc those also get included in the cache key because + // those args are eventually passed in to the baseQueryFn where the cache key gets computed. + // + // @TODO + // Ideally we wouldn't pass any args in to the endpoint at all and toast message behaviour should be controlled + // in the hooks or components that consume the RTKQ endpoints. + serializeQueryArgs: (args) => defaultSerializeQueryArgs(omit(args, 'queryArgs.notificationOptions')), tagTypes: [ 'AlertingConfiguration', 'AlertmanagerConfiguration', diff --git a/public/app/features/alerting/unified/api/featureDiscoveryApi.ts b/public/app/features/alerting/unified/api/featureDiscoveryApi.ts index 45f59b75ea7..844683dcef4 100644 --- a/public/app/features/alerting/unified/api/featureDiscoveryApi.ts +++ b/public/app/features/alerting/unified/api/featureDiscoveryApi.ts @@ -2,11 +2,16 @@ import { RulerDataSourceConfig } from 'app/types/unified-alerting'; import { AlertmanagerApiFeatures, PromApplication } from '../../../../types/unified-alerting-dto'; import { withPerformanceLogging } from '../Analytics'; -import { getRulesDataSource } from '../utils/datasource'; +import { getRulesDataSource, isGrafanaRulesSource } from '../utils/datasource'; import { alertingApi } from './alertingApi'; import { discoverAlertmanagerFeatures, discoverFeatures } from './buildInfo'; +export const GRAFANA_RULER_CONFIG: RulerDataSourceConfig = { + dataSourceName: 'grafana', + apiVersion: 'legacy', +}; + export const featureDiscoveryApi = alertingApi.injectEndpoints({ endpoints: (build) => ({ discoverAmFeatures: build.query({ @@ -22,6 +27,10 @@ export const featureDiscoveryApi = alertingApi.injectEndpoints({ discoverDsFeatures: build.query<{ rulerConfig?: RulerDataSourceConfig }, { rulesSourceName: string }>({ queryFn: async ({ rulesSourceName }) => { + if (isGrafanaRulesSource(rulesSourceName)) { + return { data: { rulerConfig: GRAFANA_RULER_CONFIG } }; + } + const dsSettings = getRulesDataSource(rulesSourceName); if (!dsSettings) { return { error: new Error(`Missing data source configuration for ${rulesSourceName}`) }; diff --git a/public/app/features/alerting/unified/api/ruler.ts b/public/app/features/alerting/unified/api/ruler.ts index df265d53bad..86bfffa72e3 100644 --- a/public/app/features/alerting/unified/api/ruler.ts +++ b/public/app/features/alerting/unified/api/ruler.ts @@ -3,7 +3,7 @@ import { lastValueFrom } from 'rxjs'; import { isObject } from '@grafana/data'; import { FetchResponse, getBackendSrv } from '@grafana/runtime'; import { RulerDataSourceConfig } from 'app/types/unified-alerting'; -import { PostableRulerRuleGroupDTO, RulerRuleGroupDTO, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; +import { RulerRuleGroupDTO, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; import { containsPathSeparator } from '../components/rule-editor/util'; import { RULER_NOT_SUPPORTED_MSG } from '../utils/constants'; @@ -111,25 +111,6 @@ function getRulerPath(rulerConfig: RulerDataSourceConfig) { return `${grafanaServerPath}/api/v1/rules`; } -// upsert a rule group. use this to update rule -export async function setRulerRuleGroup( - rulerConfig: RulerDataSourceConfig, - namespaceIdentifier: string, - group: PostableRulerRuleGroupDTO -): Promise { - const { path, params } = rulerUrlBuilder(rulerConfig).namespace(namespaceIdentifier); - await lastValueFrom( - getBackendSrv().fetch({ - method: 'POST', - url: path, - data: group, - showErrorAlert: false, - showSuccessAlert: false, - params, - }) - ); -} - export interface FetchRulerRulesFilter { dashboardUID?: string; panelId?: number; @@ -172,19 +153,6 @@ export async function fetchRulerRulesGroup( return rulerGetRequest(path, null, params); } -export async function deleteRulerRulesGroup(rulerConfig: RulerDataSourceConfig, namespace: string, groupName: string) { - const { path, params } = rulerUrlBuilder(rulerConfig).namespaceGroup(namespace, groupName); - await lastValueFrom( - getBackendSrv().fetch({ - url: path, - method: 'DELETE', - showSuccessAlert: false, - showErrorAlert: false, - params, - }) - ); -} - // false in case ruler is not supported. this is weird, but we'll work on it async function rulerGetRequest(url: string, empty: T, params?: Record): Promise { try { @@ -243,16 +211,3 @@ function isCortexErrorResponse(error: FetchResponse) { (error.data.message?.includes('group does not exist') || error.data.message?.includes('no rule groups found')) ); } - -export async function deleteNamespace(rulerConfig: RulerDataSourceConfig, namespace: string): Promise { - const { path, params } = rulerUrlBuilder(rulerConfig).namespace(namespace); - await lastValueFrom( - getBackendSrv().fetch({ - method: 'DELETE', - url: path, - showErrorAlert: false, - showSuccessAlert: false, - params, - }) - ); -} diff --git a/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx b/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx index c8e84b38183..6dcf9d49fdc 100644 --- a/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx @@ -14,7 +14,7 @@ import { AccessControlAction } from 'app/types'; import { RulerRuleGroupDTO, RulerRulesConfigDTO } from 'app/types/unified-alerting-dto'; import { alertRuleApi } from '../../api/alertRuleApi'; -import { grafanaRulerConfig } from '../../hooks/useCombinedRule'; +import { GRAFANA_RULER_CONFIG } from '../../api/featureDiscoveryApi'; import { RuleFormValues } from '../../types/rule-form'; import { DEFAULT_GROUP_EVALUATION_INTERVAL } from '../../utils/rule-form'; import { isGrafanaRulerRule } from '../../utils/rules'; @@ -33,7 +33,7 @@ export const useFolderGroupOptions = (folderUid: string, enableProvisionedGroups alertRuleApi.endpoints.rulerNamespace.useQuery( { namespace: folderUid, - rulerConfig: grafanaRulerConfig, + rulerConfig: GRAFANA_RULER_CONFIG, }, { skip: !folderUid, diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index 354e72f9a03..39ac757ad67 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -9,17 +9,16 @@ import { Button, ConfirmModal, CustomScrollbar, Spinner, Stack, useStyles2 } fro import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { useAppNotification } from 'app/core/copy/appNotification'; import { contextSrv } from 'app/core/core'; -import { useCleanup } from 'app/core/hooks/useCleanup'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; import InfoPausedRule from 'app/features/alerting/unified/components/InfoPausedRule'; import { + getRuleGroupLocationFromFormValues, getRuleGroupLocationFromRuleWithLocation, isGrafanaManagedRuleByType, isGrafanaRulerRule, isGrafanaRulerRulePaused, isRecordingRuleByType, } from 'app/features/alerting/unified/utils/rules'; -import { useDispatch } from 'app/types'; import { RuleWithLocation } from 'app/types/unified-alerting'; import { @@ -30,10 +29,8 @@ import { trackAlertRuleFormSaved, } from '../../../Analytics'; import { useDeleteRuleFromGroup } from '../../../hooks/ruleGroup/useDeleteRuleFromGroup'; -import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector'; -import { saveRuleFormAction } from '../../../state/actions'; +import { useAddRuleToRuleGroup, useUpdateRuleInRuleGroup } from '../../../hooks/ruleGroup/useUpsertRuleFromRuleGroup'; import { RuleFormType, RuleFormValues } from '../../../types/rule-form'; -import { initialAsyncRequestState } from '../../../utils/redux'; import { DEFAULT_GROUP_EVALUATION_INTERVAL, MANUAL_ROUTING_KEY, @@ -42,7 +39,10 @@ import { getDefaultQueries, ignoreHiddenQueries, normalizeDefaultAnnotations, + formValuesToRulerGrafanaRuleDTO, + formValuesToRulerRuleDTO, } from '../../../utils/rule-form'; +import { fromRulerRuleAndRuleGroupIdentifier } from '../../../utils/rule-id'; import { GrafanaRuleExporter } from '../../export/GrafanaRuleExporter'; import { AlertRuleNameAndMetric } from '../AlertRuleNameInput'; import AnnotationsStep from '../AnnotationsStep'; @@ -61,15 +61,18 @@ type Props = { export const AlertRuleForm = ({ existing, prefill }: Props) => { const styles = useStyles2(getStyles); - const dispatch = useDispatch(); const notifyApp = useAppNotification(); const [queryParams] = useQueryParams(); const [showEditYaml, setShowEditYaml] = useState(false); const [evaluateEvery, setEvaluateEvery] = useState(existing?.group.interval ?? DEFAULT_GROUP_EVALUATION_INTERVAL); + const [deleteRuleFromGroup] = useDeleteRuleFromGroup(); + const [addRuleToRuleGroup] = useAddRuleToRuleGroup(); + const [updateRuleInRuleGroup] = useUpdateRuleInRuleGroup(); const routeParams = useParams<{ type: string; id: string }>(); const ruleType = translateRouteParamToRuleType(routeParams.type); + const uidFromParams = routeParams.id; const returnTo = !queryParams.returnTo ? '/alerting/list' : String(queryParams.returnTo); @@ -103,23 +106,27 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { shouldFocusError: true, }); - const { handleSubmit, watch } = formAPI; + const { + handleSubmit, + watch, + formState: { isSubmitting }, + } = formAPI; const type = watch('type'); + const grafanaTypeRule = isGrafanaManagedRuleByType(type ?? RuleFormType.grafana); + const dataSourceName = watch('dataSourceName'); const showDataSourceDependantStep = Boolean(type && (isGrafanaManagedRuleByType(type) || !!dataSourceName)); - const submitState = useUnifiedAlertingSelector((state) => state.ruleForm.saveRule) || initialAsyncRequestState; - useCleanup((state) => (state.unifiedAlerting.ruleForm.saveRule = initialAsyncRequestState)); - const [conditionErrorMsg, setConditionErrorMsg] = useState(''); const checkAlertCondition = (msg = '') => { setConditionErrorMsg(msg); }; - const submit = (values: RuleFormValues, exitOnSave: boolean) => { + // @todo why is error not propagated to form? + const submit = async (values: RuleFormValues, exitOnSave: boolean) => { if (conditionErrorMsg !== '') { notifyApp.error(conditionErrorMsg); return; @@ -136,33 +143,39 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { } } - dispatch( - saveRuleFormAction({ - values: { - ...defaultValues, - ...values, - annotations: - values.annotations - ?.map(({ key, value }) => ({ key: key.trim(), value: value.trim() })) - .filter(({ key, value }) => !!key && !!value) ?? [], - labels: - values.labels - ?.map(({ key, value }) => ({ key: key.trim(), value: value.trim() })) - .filter(({ key }) => !!key) ?? [], - }, - existing, - redirectOnSave: exitOnSave ? returnTo : undefined, - initialAlertRuleName: defaultValues.name, - evaluateEvery: evaluateEvery, - }) - ); + const ruleDefinition = grafanaTypeRule ? formValuesToRulerGrafanaRuleDTO(values) : formValuesToRulerRuleDTO(values); + + const ruleGroupIdentifier = existing + ? getRuleGroupLocationFromRuleWithLocation(existing) + : getRuleGroupLocationFromFormValues(values); + + // @TODO what is "evaluateEvery" being used for? + // @TODO move this to a hook too to make sure the logic here is tested for regressions? + if (!existing) { + await addRuleToRuleGroup.execute(ruleGroupIdentifier, ruleDefinition, values.evaluateEvery); + } else { + const ruleIdentifier = fromRulerRuleAndRuleGroupIdentifier(ruleGroupIdentifier, existing.rule); + const targetRuleGroupIdentifier = getRuleGroupLocationFromFormValues(values); + + await updateRuleInRuleGroup.execute( + ruleGroupIdentifier, + ruleIdentifier, + ruleDefinition, + targetRuleGroupIdentifier + ); + } + + if (exitOnSave && returnTo) { + locationService.push(returnTo); + } }; const deleteRule = async () => { if (existing) { const ruleGroupIdentifier = getRuleGroupLocationFromRuleWithLocation(existing); + const ruleIdentifier = fromRulerRuleAndRuleGroupIdentifier(ruleGroupIdentifier, existing.rule); - await deleteRuleFromGroup.execute(ruleGroupIdentifier, existing.rule); + await deleteRuleFromGroup.execute(ruleGroupIdentifier, ruleIdentifier); locationService.replace(returnTo); } }; @@ -194,9 +207,9 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { type="button" size="sm" onClick={handleSubmit((values) => submit(values, false), onInvalid)} - disabled={submitState.loading} + disabled={isSubmitting} > - {submitState.loading && } + {isSubmitting && } Save rule )} @@ -205,13 +218,13 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { type="button" size="sm" onClick={handleSubmit((values) => submit(values, true), onInvalid)} - disabled={submitState.loading} + disabled={isSubmitting} > - {submitState.loading && } + {isSubmitting && } Save rule and exit - @@ -225,7 +238,7 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { variant="secondary" type="button" onClick={() => setShowEditYaml(true)} - disabled={submitState.loading} + disabled={isSubmitting} size="sm" > Edit YAML diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx index 67f8db90e1a..87acb2ba535 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx @@ -2,29 +2,23 @@ import { ReactNode } from 'react'; import { Route } from 'react-router-dom'; import { ui } from 'test/helpers/alertingRuleEditor'; import { clickSelectOption } from 'test/helpers/selectOptionInTest'; -import { render, screen, waitFor, waitForElementToBeRemoved, userEvent } from 'test/test-utils'; +import { render, screen, waitForElementToBeRemoved, userEvent } from 'test/test-utils'; import { byRole } from 'testing-library-selector'; import { config } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; import RuleEditor from 'app/features/alerting/unified/RuleEditor'; -import * as ruler from 'app/features/alerting/unified/api/ruler'; import { setupMswServer } from 'app/features/alerting/unified/mockApi'; import { grantUserPermissions, mockDataSource } from 'app/features/alerting/unified/mocks'; import { setAlertmanagerChoices } from 'app/features/alerting/unified/mocks/server/configure'; +import { captureRequests, serializeRequests } from 'app/features/alerting/unified/mocks/server/events'; import { FOLDER_TITLE_HAPPY_PATH } from 'app/features/alerting/unified/mocks/server/handlers/search'; import { AlertmanagerProvider } from 'app/features/alerting/unified/state/AlertmanagerContext'; -import { - DataSourceType, - GRAFANA_DATASOURCE_NAME, - GRAFANA_RULES_SOURCE_NAME, -} from 'app/features/alerting/unified/utils/datasource'; -import { getDefaultQueries } from 'app/features/alerting/unified/utils/rule-form'; +import { DataSourceType, GRAFANA_DATASOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; -import { GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto'; -import { grafanaRulerEmptyGroup, grafanaRulerNamespace2 } from '../../../../mocks/grafanaRulerApi'; +import { grafanaRulerEmptyGroup } from '../../../../mocks/grafanaRulerApi'; import { setupDataSources } from '../../../../testSetup/datasources'; jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({ @@ -33,12 +27,6 @@ jest.mock('app/core/components/AppChrome/AppChromeUpdate', () => ({ jest.setTimeout(60 * 1000); -const mocks = { - api: { - setRulerRuleGroup: jest.spyOn(ruler, 'setRulerRuleGroup'), - }, -}; - setupMswServer(); const dataSources = { @@ -91,6 +79,7 @@ describe('Can create a new grafana managed alert using simplified routing', () = it('cannot create new grafana managed alert when using simplified routing and not selecting a contact point', async () => { const user = userEvent.setup(); + const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/')); renderSimplifiedRuleEditor(); await waitForElementToBeRemoved(screen.queryAllByTestId('Spinner')); @@ -106,7 +95,9 @@ describe('Can create a new grafana managed alert using simplified routing', () = // save and check that call to backend was not made await user.click(ui.buttons.saveAndExit.get()); expect(await screen.findByText('Contact point is required.')).toBeInTheDocument(); - expect(mocks.api.setRulerRuleGroup).not.toHaveBeenCalled(); + const capturedRequests = await capture; + + expect(capturedRequests).toHaveLength(0); }); it('simplified routing is not available when Grafana AM is not enabled', async () => { @@ -120,6 +111,7 @@ describe('Can create a new grafana managed alert using simplified routing', () = it('can create new grafana managed alert when using simplified routing and selecting a contact point', async () => { const user = userEvent.setup(); const contactPointName = 'lotsa-emails'; + const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/')); renderSimplifiedRuleEditor(); await waitForElementToBeRemoved(screen.queryAllByTestId('Spinner')); @@ -136,38 +128,10 @@ describe('Can create a new grafana managed alert using simplified routing', () = // save and check what was sent to backend await user.click(ui.buttons.saveAndExit.get()); - await waitFor(() => expect(mocks.api.setRulerRuleGroup).toHaveBeenCalled()); - expect(mocks.api.setRulerRuleGroup).toHaveBeenCalledWith( - { dataSourceName: GRAFANA_RULES_SOURCE_NAME, apiVersion: 'legacy' }, - grafanaRulerNamespace2.uid, - { - interval: grafanaRulerEmptyGroup.interval, - name: grafanaRulerEmptyGroup.name, - rules: [ - { - annotations: {}, - labels: {}, - for: '1m', - grafana_alert: { - condition: 'B', - data: getDefaultQueries(), - exec_err_state: GrafanaAlertStateDecision.Error, - is_paused: false, - no_data_state: 'NoData', - title: 'my great new rule', - notification_settings: { - group_by: undefined, - group_interval: undefined, - group_wait: undefined, - mute_timings: undefined, - receiver: contactPointName, - repeat_interval: undefined, - }, - }, - }, - ], - } - ); + const requests = await capture; + + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); }); describe('alertingApiServer enabled', () => { diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/__snapshots__/SimplifiedRuleEditor.test.tsx.snap b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/__snapshots__/SimplifiedRuleEditor.test.tsx.snap new file mode 100644 index 00000000000..594f329ed16 --- /dev/null +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/__snapshots__/SimplifiedRuleEditor.test.tsx.snap @@ -0,0 +1,116 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Can create a new grafana managed alert using simplified routing can create new grafana managed alert when using simplified routing and selecting a contact point 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "empty-group", + "rules": [ + { + "annotations": {}, + "for": "1m", + "grafana_alert": { + "condition": "B", + "data": [ + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "A", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "A", + "reducer": "last", + "refId": "A", + "type": "reduce", + }, + "queryType": "", + "refId": "A", + }, + { + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + ], + "type": "gt", + }, + "operator": { + "type": "and", + }, + "query": { + "params": [ + "B", + ], + }, + "reducer": { + "params": [], + "type": "last", + }, + "type": "query", + }, + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__", + }, + "expression": "A", + "refId": "B", + "type": "threshold", + }, + "queryType": "", + "refId": "B", + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "no_data_state": "NoData", + "notification_settings": { + "receiver": "lotsa-emails", + }, + "title": "my great new rule", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/6abdb25bc1eb?subtype=cortex", + }, +] +`; diff --git a/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx b/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx index 9821f7483b4..3e2e87a4438 100644 --- a/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx +++ b/public/app/features/alerting/unified/components/rule-viewer/DeleteModal.tsx @@ -7,6 +7,7 @@ import { CombinedRule } from 'app/types/unified-alerting'; import { useDeleteRuleFromGroup } from '../../hooks/ruleGroup/useDeleteRuleFromGroup'; import { fetchPromAndRulerRulesAction } from '../../state/actions'; +import { fromRulerRuleAndRuleGroupIdentifier } from '../../utils/rule-id'; import { getRuleGroupLocationFromCombinedRule } from '../../utils/rules'; type DeleteModalHook = [JSX.Element, (rule: CombinedRule) => void, () => void]; @@ -29,12 +30,14 @@ export const useDeleteModal = (redirectToListView = false): DeleteModalHook => { return; } - const location = getRuleGroupLocationFromCombinedRule(rule); - await deleteRuleFromGroup.execute(location, rule.rulerRule); + const ruleGroupIdentifier = getRuleGroupLocationFromCombinedRule(rule); + const ruleIdentifier = fromRulerRuleAndRuleGroupIdentifier(ruleGroupIdentifier, rule.rulerRule); + + await deleteRuleFromGroup.execute(ruleGroupIdentifier, ruleIdentifier); // refetch rules for this rules source // @TODO remove this when we moved everything to RTKQ – then the endpoint will simply invalidate the tags - dispatch(fetchPromAndRulerRulesAction({ rulesSourceName: location.dataSourceName })); + dispatch(fetchPromAndRulerRulesAction({ rulesSourceName: ruleGroupIdentifier.dataSourceName })); dismissModal(); diff --git a/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.test.tsx b/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.test.tsx deleted file mode 100644 index 8744867ab72..00000000000 --- a/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.test.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { reorder } from './ReorderRuleGroupModal'; - -describe('test reorder', () => { - it('should reorder arrays', () => { - const original = [1, 2, 3]; - const expected = [1, 3, 2]; - - expect(reorder(original, 1, 2)).toEqual(expected); - expect(original).not.toEqual(expected); // make sure we've not mutated the original - }); -}); diff --git a/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx b/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx index 278be0bf5dd..3a40868f20e 100644 --- a/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx +++ b/public/app/features/alerting/unified/components/rules/ReorderRuleGroupModal.tsx @@ -8,22 +8,30 @@ import { DropResult, } from '@hello-pangea/dnd'; import cx from 'classnames'; -import { compact } from 'lodash'; -import { useCallback, useState } from 'react'; +import { produce } from 'immer'; +import { useCallback, useEffect, useState } from 'react'; import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { Badge, Icon, Modal, Tooltip, useStyles2 } from '@grafana/ui'; -import { useCombinedRuleNamespaces } from 'app/features/alerting/unified/hooks/useCombinedRuleNamespaces'; -import { dispatch } from 'app/store/store'; -import { CombinedRule, CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; - -import { updateRulesOrder } from '../../state/actions'; -import { getRulesSourceName, isCloudRulesSource } from '../../utils/datasource'; +import { Badge, Button, Icon, Modal, Tooltip, useStyles2 } from '@grafana/ui'; +import { Trans } from 'app/core/internationalization'; +import { dispatch, getState } from 'app/store/store'; +import { CombinedRuleGroup, CombinedRuleNamespace, RuleGroupIdentifier } from 'app/types/unified-alerting'; +import { RulerRuleDTO } from 'app/types/unified-alerting-dto'; + +import { alertRuleApi } from '../../api/alertRuleApi'; +import { useReorderRuleForRuleGroup } from '../../hooks/ruleGroup/useUpdateRuleGroup'; +import { isLoading } from '../../hooks/useAsync'; +import { swapItems, SwapOperation } from '../../reducers/ruler/ruleGroups'; +import { fetchRulerRulesAction, getDataSourceRulerConfig } from '../../state/actions'; +import { isCloudRulesSource } from '../../utils/datasource'; import { hashRulerRule } from '../../utils/rule-id'; -import { isAlertingRule, isRecordingRule } from '../../utils/rules'; - -import { AlertStateTag } from './AlertStateTag'; +import { + isAlertingRulerRule, + isGrafanaRulerRule, + isRecordingRulerRule, + rulesSourceToDataSourceName, +} from '../../utils/rules'; interface ModalProps { namespace: CombinedRuleNamespace; @@ -32,24 +40,36 @@ interface ModalProps { folderUid?: string; } -type CombinedRuleWithUID = { uid: string } & CombinedRule; +type RulerRuleWithUID = { uid: string } & RulerRuleDTO; export const ReorderCloudGroupModal = (props: ModalProps) => { + const styles = useStyles2(getStyles); const { group, namespace, onClose, folderUid } = props; + const [operations, setOperations] = useState>([]); + + const [reorderRulesInGroup, reorderState] = useReorderRuleForRuleGroup(); + const isUpdating = isLoading(reorderState); // The list of rules might have been filtered before we get to this reordering modal - // We need to grab the full (unfiltered) list so we are able to reorder via the API without - // deleting any rules (as they otherwise would have been omitted from the payload) - const unfilteredNamespaces = useCombinedRuleNamespaces(); - const matchedNamespace = unfilteredNamespaces.find( - (ns) => ns.rulesSource === namespace.rulesSource && ns.name === namespace.name + // We need to grab the full (unfiltered) list + const dataSourceName = rulesSourceToDataSourceName(namespace.rulesSource); + const rulerConfig = getDataSourceRulerConfig(getState, dataSourceName); + const { currentData: ruleGroup, isLoading: loadingRules } = alertRuleApi.endpoints.getRuleGroupForNamespace.useQuery( + { + rulerConfig, + namespace: folderUid ?? namespace.name, + group: group.name, + }, + { refetchOnMountOrArgChange: true } ); - const matchedGroup = matchedNamespace?.groups.find((g) => g.name === group.name); - const [pending, setPending] = useState(false); - const [rulesList, setRulesList] = useState(matchedGroup?.rules || []); + const [rulesList, setRulesList] = useState([]); - const styles = useStyles2(getStyles); + useEffect(() => { + if (ruleGroup) { + setRulesList(ruleGroup?.rules); + } + }, [ruleGroup]); const onDragEnd = useCallback( (result: DropResult) => { @@ -58,39 +78,50 @@ export const ReorderCloudGroupModal = (props: ModalProps) => { return; } - const sameIndex = result.destination.index === result.source.index; - if (sameIndex) { - return; - } - - const newOrderedRules = reorder(rulesList, result.source.index, result.destination.index); - setRulesList(newOrderedRules); // optimistically update the new rules list - - const rulesSourceName = getRulesSourceName(namespace.rulesSource); - const rulerRules = compact(newOrderedRules.map((rule) => rule.rulerRule)); + const swapOperation: SwapOperation = [result.source.index, result.destination.index]; - setPending(true); - dispatch( - updateRulesOrder({ - namespaceName: namespace.name, - groupName: group.name, - rulesSourceName: rulesSourceName, - newRules: rulerRules, - folderUid: folderUid || namespace.name, + // add old index and new index to the modifications object + setOperations( + produce(operations, (draft) => { + draft.push(swapOperation); }) - ) - .unwrap() - .finally(() => { - setPending(false); - }); + ); + + // re-order the rules list for the UI rendering + const newOrderedRules = produce(rulesList, (draft) => { + swapItems(draft, swapOperation); + }); + setRulesList(newOrderedRules); }, - [group.name, namespace.name, namespace.rulesSource, rulesList, folderUid] + [rulesList, operations] ); + const updateRulesOrder = useCallback(async () => { + const ruleGroupIdentifier: RuleGroupIdentifier = { + dataSourceName: rulesSourceToDataSourceName(namespace.rulesSource), + groupName: group.name, + namespaceName: folderUid ?? namespace.name, + }; + + await reorderRulesInGroup.execute(ruleGroupIdentifier, operations); + // TODO: Remove once RTKQ is more prevalently used + await dispatch(fetchRulerRulesAction({ rulesSourceName: dataSourceName })); + onClose(); + }, [ + namespace.rulesSource, + namespace.name, + group.name, + folderUid, + reorderRulesInGroup, + operations, + dataSourceName, + onClose, + ]); + // assign unique but stable identifiers to each (alerting / recording) rule - const rulesWithUID: CombinedRuleWithUID[] = rulesList.map((rule) => ({ - ...rule, - uid: String(hashRulerRule(rule.rulerRule!)), // TODO fix this coercion? + const rulesWithUID: RulerRuleWithUID[] = rulesList.map((rulerRule) => ({ + ...rulerRule, + uid: hashRulerRule(rulerRule), })); return ( @@ -101,37 +132,50 @@ export const ReorderCloudGroupModal = (props: ModalProps) => { onDismiss={onClose} onClickBackdrop={onClose} > - - ( - - )} - > - {(droppableProvided: DroppableProvided) => ( -
0 && ( + <> + + ( + + )} > - {rulesWithUID.map((rule, index) => ( - - {(provided: DraggableProvided) => } - - ))} - {droppableProvided.placeholder} -
- )} -
-
+ {(droppableProvided: DroppableProvided) => ( +
+ {rulesWithUID.map((rule, index) => ( + + {(provided: DraggableProvided) => } + + ))} + {droppableProvided.placeholder} +
+ )} + + + + + + + + )} ); }; interface ListItemProps extends React.HTMLAttributes { provided: DraggableProvided; - rule: CombinedRule; + rule: RulerRuleDTO; isClone?: boolean; isDragging?: boolean; } @@ -139,6 +183,7 @@ interface ListItemProps extends React.HTMLAttributes { const ListItem = ({ provided, rule, isClone = false, isDragging = false }: ListItemProps) => { const styles = useStyles2(getStyles); + // @TODO does this work with Grafana-managed recording rules too? Double check that. return (
- {isAlertingRule(rule.promRule) && } - {isRecordingRule(rule.promRule) && } -
{rule.name}
- + {isGrafanaRulerRule(rule) &&
{rule.grafana_alert.title}
} + {isRecordingRulerRule(rule) && ( + <> +
{rule.record}
+ + + )} + {isAlertingRulerRule(rule) &&
{rule.alert}
} +
); }; @@ -235,11 +285,3 @@ const getStyles = (theme: GrafanaTheme2) => ({ height: theme.spacing(2), }), }); - -export function reorder(rules: T[], startIndex: number, endIndex: number): T[] { - const result = Array.from(rules); - const [removed] = result.splice(startIndex, 1); - result.splice(endIndex, 0, removed); - - return result; -} diff --git a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx index bb66ef6421e..5ae024d814c 100644 --- a/public/app/features/alerting/unified/components/rules/RulesGroup.tsx +++ b/public/app/features/alerting/unified/components/rules/RulesGroup.tsx @@ -6,17 +6,16 @@ import * as React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { Badge, ConfirmModal, Icon, Spinner, Stack, Tooltip, useStyles2 } from '@grafana/ui'; -import { useDispatch } from 'app/types'; -import { CombinedRuleGroup, CombinedRuleNamespace } from 'app/types/unified-alerting'; +import { CombinedRuleGroup, CombinedRuleNamespace, RuleGroupIdentifier } from 'app/types/unified-alerting'; import { LogMessages, logInfo } from '../../Analytics'; +import { useDeleteRuleGroup } from '../../hooks/ruleGroup/useDeleteRuleGroup'; import { useFolder } from '../../hooks/useFolder'; import { useHasRuler } from '../../hooks/useHasRuler'; -import { deleteRulesGroupAction } from '../../state/actions'; import { useRulesAccess } from '../../utils/accessControlHooks'; import { GRAFANA_RULES_SOURCE_NAME, isCloudRulesSource } from '../../utils/datasource'; import { makeFolderLink, makeFolderSettingsLink } from '../../utils/misc'; -import { isFederatedRuleGroup, isGrafanaRulerRule } from '../../utils/rules'; +import { isFederatedRuleGroup, isGrafanaRulerRule, rulesSourceToDataSourceName } from '../../utils/rules'; import { CollapseToggle } from '../CollapseToggle'; import { RuleLocation } from '../RuleLocation'; import { GrafanaRuleFolderExporter } from '../export/GrafanaRuleFolderExporter'; @@ -40,8 +39,8 @@ interface Props { export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }: Props) => { const { rulesSource } = namespace; - const dispatch = useDispatch(); const styles = useStyles2(getStyles); + const [deleteRuleGroup] = useDeleteRuleGroup(); const [isEditingGroup, setIsEditingGroup] = useState(false); const [isDeletingGroup, setIsDeletingGroup] = useState(false); @@ -74,8 +73,13 @@ export const RulesGroup = React.memo(({ group, namespace, expandAll, viewMode }: const isListView = viewMode === 'list'; const isGroupView = viewMode === 'grouped'; - const deleteGroup = () => { - dispatch(deleteRulesGroupAction(namespace, group)); + const deleteGroup = async () => { + const namespaceName = decodeGrafanaNamespace(namespace).name; + const groupName = group.name; + const dataSourceName = rulesSourceToDataSourceName(namespace.rulesSource); + + const ruleGroupIdentifier: RuleGroupIdentifier = { namespaceName, groupName, dataSourceName }; + await deleteRuleGroup.execute(ruleGroupIdentifier); setIsDeletingGroup(false); }; diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useAddRuleToRuleGroup.test.tsx.snap b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useAddRuleToRuleGroup.test.tsx.snap new file mode 100644 index 00000000000..8d3ddb718e3 --- /dev/null +++ b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useAddRuleToRuleGroup.test.tsx.snap @@ -0,0 +1,206 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Creating a Data source managed rule should be able to add a rule to a existing rule group 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "group-1", + "rules": [ + { + "alert": "alert1", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "severity": "warning", + }, + }, + { + "alert": "my new rule", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "severity": "warning", + }, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-1?subtype=mimir", + }, +] +`; + +exports[`Creating a Data source managed rule should be able to add a rule to a new rule group 1`] = ` +[ + { + "body": { + "interval": "15m", + "name": "new group", + "rules": [ + { + "annotations": {}, + "for": "", + "grafana_alert": { + "condition": "", + "data": [], + "exec_err_state": "Error", + "namespace_uid": "NAMESPACE_UID", + "no_data_state": "NoData", + "rule_group": "my-group", + "title": "my new rule", + "uid": "mock-rule-uid-123", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/new%20namespace?subtype=mimir", + }, +] +`; + +exports[`Creating a Grafana managed rule should be able to add a rule to a existing rule group 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "grafana-group-1", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "Grafana-rule", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + { + "annotations": {}, + "for": "", + "grafana_alert": { + "condition": "", + "data": [], + "exec_err_state": "Error", + "namespace_uid": "NAMESPACE_UID", + "no_data_state": "NoData", + "rule_group": "my-group", + "title": "my new rule", + "uid": "mock-rule-uid-123", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; + +exports[`Creating a Grafana managed rule should be able to add a rule to a new rule group 1`] = ` +[ + { + "body": { + "interval": "15m", + "name": "grafana-group-3", + "rules": [ + { + "annotations": {}, + "for": "", + "grafana_alert": { + "condition": "", + "data": [], + "exec_err_state": "Error", + "namespace_uid": "NAMESPACE_UID", + "no_data_state": "NoData", + "rule_group": "my-group", + "title": "my new rule", + "uid": "mock-rule-uid-123", + }, + "labels": {}, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useDeleteRuleGroup.test.tsx.snap b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useDeleteRuleGroup.test.tsx.snap new file mode 100644 index 00000000000..b6ecad04404 --- /dev/null +++ b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useDeleteRuleGroup.test.tsx.snap @@ -0,0 +1,33 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Grafana managed should be able to delete a Grafana managed rule group 1`] = ` +[ + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "DELETE", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef/grafana-group-1?subtype=cortex", + }, +] +`; + +exports[`data-source managed should be able to delete a data-source managed rule group 1`] = ` +[ + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "DELETE", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-1/group-1?subtype=mimir", + }, +] +`; diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useMoveRuleFromRuleGroup.test.tsx.snap b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useMoveRuleFromRuleGroup.test.tsx.snap new file mode 100644 index 00000000000..2374935f47a --- /dev/null +++ b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useMoveRuleFromRuleGroup.test.tsx.snap @@ -0,0 +1,206 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Moving a Data source managed rule should move a rule in an existing group to a new group 1`] = ` +[ + { + "body": { + "name": "entirely new group name", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "updated rule title", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-1?subtype=mimir", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "DELETE", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-1/group-1?subtype=mimir", + }, +] +`; + +exports[`Moving a Data source managed rule should move a rule in an existing group to another existing group 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "group-3", + "rules": [ + { + "alert": "rule 3", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "severity": "warning", + }, + }, + { + "alert": "rule 4", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "severity": "warning", + }, + }, + { + "alert": "alert1", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "severity": "warning", + }, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-1?subtype=mimir", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "DELETE", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-1/group-1?subtype=mimir", + }, +] +`; + +exports[`Moving a Grafana managed rule should move a rule from an existing group to another group in the same namespace 1`] = ` +[ + { + "body": { + "name": "empty-group", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "Grafana-rule", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useUpdateRuleGroup.test.tsx.snap b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useUpdateRuleGroup.test.tsx.snap index 62c6e987364..5357d0c6a6d 100644 --- a/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useUpdateRuleGroup.test.tsx.snap +++ b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useUpdateRuleGroup.test.tsx.snap @@ -1,5 +1,83 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`reorder rules for rule group should correctly reorder rules 1`] = ` +[ + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "https://mimir.local:9000/api/v1/status/buildinfo", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-2/group-3?subtype=mimir", + }, + { + "body": { + "interval": "1m", + "name": "group-3", + "rules": [ + { + "alert": "rule 4", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "severity": "warning", + }, + }, + { + "alert": "rule 3", + "annotations": { + "summary": "test alert", + }, + "expr": "up = 1", + "labels": { + "severity": "warning", + }, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-2?subtype=mimir", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "GET", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-2/group-3?subtype=mimir", + }, +] +`; + exports[`useUpdateRuleGroupConfiguration should be able to move a Data Source managed rule group 1`] = ` [ { diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useUpdateRuleInRuleGroup.test.tsx.snap b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useUpdateRuleInRuleGroup.test.tsx.snap new file mode 100644 index 00000000000..1b0143c619e --- /dev/null +++ b/public/app/features/alerting/unified/hooks/ruleGroup/__snapshots__/useUpdateRuleInRuleGroup.test.tsx.snap @@ -0,0 +1,311 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Updating a Data source managed rule should be able to move a rule if target group is different from current group 1`] = ` +[ + { + "body": { + "name": "a new group", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "updated rule title", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-1?subtype=mimir", + }, + { + "body": "", + "headers": [ + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "DELETE", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-1/group-1?subtype=mimir", + }, +] +`; + +exports[`Updating a Data source managed rule should update a rule in an existing group 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "group-1", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "updated rule title", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/mimir/api/v1/rules/namespace-1?subtype=mimir", + }, +] +`; + +exports[`Updating a Grafana managed rule should move a rule in to another group 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "grafana-group-2", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "Grafana-rule", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "updated rule title", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; + +exports[`Updating a Grafana managed rule should update a rule in an existing group 1`] = ` +[ + { + "body": { + "interval": "1m", + "name": "grafana-group-1", + "rules": [ + { + "annotations": { + "summary": "Test alert", + }, + "for": "5m", + "grafana_alert": { + "condition": "A", + "data": [ + { + "datasourceUid": "datasource-uid", + "model": { + "datasource": { + "type": "prometheus", + "uid": "datasource-uid", + }, + "expression": "vector(1)", + "queryType": "alerting", + "refId": "A", + }, + "queryType": "alerting", + "refId": "A", + "relativeTimeRange": { + "from": 1000, + "to": 2000, + }, + }, + ], + "exec_err_state": "Error", + "is_paused": false, + "namespace_uid": "uuid020c61ef", + "no_data_state": "NoData", + "rule_group": "grafana-group-1", + "title": "updated rule title", + "uid": "4d7125fee983", + }, + "labels": { + "region": "nasa", + "severity": "critical", + }, + }, + ], + }, + "headers": [ + [ + "content-type", + "application/json", + ], + [ + "accept", + "application/json, text/plain, */*", + ], + ], + "method": "POST", + "url": "http://localhost/api/ruler/grafana/api/v1/rules/uuid020c61ef?subtype=cortex", + }, +] +`; diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/useAddRuleToRuleGroup.test.tsx b/public/app/features/alerting/unified/hooks/ruleGroup/useAddRuleToRuleGroup.test.tsx new file mode 100644 index 00000000000..acbcc25052c --- /dev/null +++ b/public/app/features/alerting/unified/hooks/ruleGroup/useAddRuleToRuleGroup.test.tsx @@ -0,0 +1,157 @@ +import { render } from 'test/test-utils'; +import { byRole, byText } from 'testing-library-selector'; + +import { AccessControlAction } from 'app/types/accessControl'; +import { RuleGroupIdentifier } from 'app/types/unified-alerting'; +import { PostableRuleDTO } from 'app/types/unified-alerting-dto'; + +import { setupMswServer } from '../../mockApi'; +import { grantUserPermissions, mockGrafanaRulerRule, mockRulerAlertingRule } from '../../mocks'; +import { grafanaRulerGroupName, grafanaRulerNamespace } from '../../mocks/grafanaRulerApi'; +import { GROUP_1, NAMESPACE_1 } from '../../mocks/mimirRulerApi'; +import { mimirDataSource } from '../../mocks/server/configure'; +import { MIMIR_DATASOURCE_UID } from '../../mocks/server/constants'; +import { captureRequests, serializeRequests } from '../../mocks/server/events'; +import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; +import { SerializeState } from '../useAsync'; + +import { useAddRuleToRuleGroup } from './useUpsertRuleFromRuleGroup'; + +setupMswServer(); + +beforeAll(() => { + grantUserPermissions([ + AccessControlAction.AlertingRuleExternalRead, + AccessControlAction.AlertingRuleExternalWrite, + AccessControlAction.AlertingRuleRead, + AccessControlAction.AlertingRuleCreate, + ]); +}); + +describe('Creating a Grafana managed rule', () => { + it('should be able to add a rule to a existing rule group', async () => { + const capture = captureRequests((r) => r.method === 'POST'); + + const ruleGroupID: RuleGroupIdentifier = { + dataSourceName: GRAFANA_RULES_SOURCE_NAME, + groupName: grafanaRulerGroupName, + namespaceName: grafanaRulerNamespace.uid, + }; + + const rule = mockGrafanaRulerRule({ title: 'my new rule' }); + + const { user } = render(); + await user.click(byRole('button').get()); + + expect(await byText(/success/i).find()).toBeInTheDocument(); + + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); + + it('should be able to add a rule to a new rule group', async () => { + const capture = captureRequests((r) => r.method === 'POST'); + + const ruleGroupID: RuleGroupIdentifier = { + dataSourceName: GRAFANA_RULES_SOURCE_NAME, + groupName: 'grafana-group-3', + namespaceName: grafanaRulerNamespace.uid, + }; + + const rule = mockGrafanaRulerRule({ title: 'my new rule' }); + + const { user } = render(); + await user.click(byRole('button').get()); + + expect(await byText(/success/i).find()).toBeInTheDocument(); + + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); + + it('should not be able to add a rule to a non-existing namespace', async () => { + const ruleGroupID: RuleGroupIdentifier = { + dataSourceName: GRAFANA_RULES_SOURCE_NAME, + groupName: grafanaRulerGroupName, + namespaceName: 'does-not-exist', + }; + + const rule = mockGrafanaRulerRule({ title: 'my new rule' }); + + const { user } = render(); + await user.click(byRole('button').get()); + + expect(await byText(/error/i).find()).toBeInTheDocument(); + }); +}); + +describe('Creating a Data source managed rule', () => { + beforeEach(() => { + mimirDataSource(); + }); + + it('should be able to add a rule to a existing rule group', async () => { + const capture = captureRequests((r) => r.method === 'POST'); + + const ruleGroupID: RuleGroupIdentifier = { + dataSourceName: MIMIR_DATASOURCE_UID, + groupName: GROUP_1, + namespaceName: NAMESPACE_1, + }; + + const rule = mockRulerAlertingRule({ alert: 'my new rule' }); + + const { user } = render(); + await user.click(byRole('button').get()); + + expect(await byText(/success/i).find()).toBeInTheDocument(); + + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); + + it('should be able to add a rule to a new rule group', async () => { + const capture = captureRequests((r) => r.method === 'POST'); + + const ruleGroupID: RuleGroupIdentifier = { + dataSourceName: MIMIR_DATASOURCE_UID, + groupName: 'new group', + namespaceName: 'new namespace', + }; + + const rule = mockGrafanaRulerRule({ title: 'my new rule' }); + + const { user } = render(); + await user.click(byRole('button').get()); + + expect(await byText(/success/i).find()).toBeInTheDocument(); + + const requests = await capture; + const serializedRequests = await serializeRequests(requests); + expect(serializedRequests).toMatchSnapshot(); + }); +}); + +type AddRuleTestComponentProps = { + ruleGroupIdentifier: RuleGroupIdentifier; + rule: PostableRuleDTO; + interval?: string; +}; + +const AddRuleTestComponent = ({ ruleGroupIdentifier, rule, interval }: AddRuleTestComponentProps) => { + const [addRule, requestState] = useAddRuleToRuleGroup(); + + const onClick = () => { + addRule.execute(ruleGroupIdentifier, rule, interval); + }; + + return ( + <> +