setTooltipCoords({ clientX, clientY })}
+ onClick={tooltipOnClickHandler(setTooltipCoords)}
>
{shouldShowLink ? (
renderSingleLink(links[0], renderComponent())
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx
index 348d0aa168c..bc62e266b12 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/ImageCell.tsx
@@ -7,7 +7,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../../../themes/ThemeContext';
import { DataLinksActionsTooltip, renderSingleLink } from '../../DataLinksActionsTooltip';
import { TableCellDisplayMode } from '../../types';
-import { DataLinksActionsTooltipCoords, getDataLinksActionsTooltipUtils } from '../../utils';
+import { DataLinksActionsTooltipCoords, getDataLinksActionsTooltipUtils, tooltipOnClickHandler } from '../../utils';
import { ImageCellProps } from '../types';
import { getCellLinks } from '../utils';
@@ -33,9 +33,7 @@ export const ImageCell = ({ cellOptions, field, height, justifyContent, value, r
{
- setTooltipCoords({ clientX, clientY });
- }}
+ onClick={tooltipOnClickHandler(setTooltipCoords)}
>
{shouldShowLink ? (
renderSingleLink(links[0], img)
diff --git a/packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx b/packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx
index 025cf19bac3..2506255cb03 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/Cells/JSONCell.tsx
@@ -6,7 +6,7 @@ import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '../../../../themes/ThemeContext';
import { DataLinksActionsTooltip, renderSingleLink } from '../../DataLinksActionsTooltip';
-import { DataLinksActionsTooltipCoords, getDataLinksActionsTooltipUtils } from '../../utils';
+import { tooltipOnClickHandler, DataLinksActionsTooltipCoords, getDataLinksActionsTooltipUtils } from '../../utils';
import { JSONCellProps } from '../types';
import { getCellLinks } from '../utils';
@@ -43,7 +43,7 @@ export const JSONCell = ({ value, justifyContent, field, rowIdx, actions }: JSON
// eslint-disable-next-line jsx-a11y/click-events-have-key-events,jsx-a11y/no-static-element-interactions
setTooltipCoords({ clientX, clientY })}
+ onClick={tooltipOnClickHandler(setTooltipCoords)}
style={{ cursor: hasMultipleLinksOrActions ? 'context-menu' : 'auto' }}
>
{shouldShowLink ? (
diff --git a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
index d2e38be4f58..625a4b4b106 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
+++ b/packages/grafana-ui/src/components/Table/TableNG/TableNG.tsx
@@ -1,7 +1,7 @@
import 'react-data-grid/lib/styles.css';
import { css, cx } from '@emotion/css';
import { Property } from 'csstype';
-import { Key, useLayoutEffect, useMemo, useState } from 'react';
+import { Key, ReactNode, useLayoutEffect, useMemo, useState } from 'react';
import {
Cell,
CellRendererProps,
@@ -22,7 +22,7 @@ import { MenuItem } from '../../Menu/MenuItem';
import { Pagination } from '../../Pagination/Pagination';
import { PanelContext, usePanelContext } from '../../PanelChrome';
import { TableCellInspector, TableCellInspectorMode } from '../TableCellInspector';
-import { CellColors } from '../types';
+import { CellColors, TableCellDisplayMode } from '../types';
import { HeaderCell } from './Cells/HeaderCell';
import { RowExpander } from './Cells/RowExpander';
@@ -55,6 +55,8 @@ import {
getCellOptions,
} from './utils';
+type CellRootRenderer = (key: React.Key, props: CellRendererProps
) => React.ReactNode;
+
export function TableNG(props: TableNGProps) {
const {
cellHeight,
@@ -137,10 +139,6 @@ export function TableNG(props: TableNGProps) {
// vt scrollbar accounting for column auto-sizing
const visibleFields = useMemo(() => getVisibleFields(data.fields), [data.fields]);
- const visibleFieldsByDisplayName: Record = useMemo(
- () => visibleFields.reduce((acc, f) => ({ ...acc, [getDisplayName(f)]: f }), {}),
- [visibleFields]
- );
const availableWidth = useMemo(
() => (hasNestedFrames ? width - COLUMN.EXPANDER_WIDTH : width),
[width, hasNestedFrames]
@@ -175,11 +173,6 @@ export function TableNG(props: TableNGProps) {
[data, enableSharedCrosshair, expandedRows, panelContext]
);
- const renderCell = useMemo(
- () => renderCellFactory(columnTypes, applyToRowBgFn, rowHeight, textWraps, theme, visibleFieldsByDisplayName),
- [columnTypes, applyToRowBgFn, rowHeight, textWraps, theme, visibleFieldsByDisplayName]
- );
-
const commonDataGridProps = useMemo(
() =>
({
@@ -240,9 +233,19 @@ export function TableNG(props: TableNGProps) {
]
);
- const columns = useMemo((): TableColumn[] => {
- const columnsFromFields = (f: Field[], w: number[]): TableColumn[] =>
- f.map((field, i): TableColumn => {
+ interface Schema {
+ columns: TableColumn[];
+ cellRootRenderers: Record;
+ }
+
+ const { columns, cellRootRenderers } = useMemo(() => {
+ const fromFields = (f: Field[], widths: number[]) => {
+ const result: Schema = {
+ columns: [],
+ cellRootRenderers: {},
+ };
+
+ f.forEach((field, i) => {
const justifyContent = getTextAlign(field);
const footerStyles = getFooterStyles(justifyContent);
const displayName = getDisplayName(field);
@@ -253,7 +256,7 @@ export function TableNG(props: TableNGProps) {
const cellInspect = Boolean(field.config.custom?.inspect);
const showFilters = Boolean(field.config.filterable && onCellFilterAdded != null);
const showActions = cellInspect || showFilters;
- const width = w[i];
+ const width = widths[i];
const frame = data;
// helps us avoid string cx and emotion per-cell
@@ -265,54 +268,99 @@ export function TableNG(props: TableNGProps) {
)
: undefined;
- return {
+ const cellType = cellOptions.type;
+ const fieldType = columnTypes[displayName];
+ const shouldWrap = textWraps[displayName];
+ const shouldOverflow = shouldTextOverflow(fieldType, cellType, shouldWrap, cellInspect);
+
+ let lastRowIdx = -1;
+ let _rowHeight = 0;
+
+ // this fires first
+ const renderCellRoot = (key: Key, props: CellRendererProps): ReactNode => {
+ const rowIdx = props.row.__index;
+ const value = props.row[props.column.key];
+
+ // meh, this should be cached by the renderRow() call?
+ if (rowIdx !== lastRowIdx) {
+ _rowHeight = typeof rowHeight === 'function' ? rowHeight(props.row) : rowHeight;
+ lastRowIdx = rowIdx;
+ }
+
+ let colors: CellColors;
+
+ if (applyToRowBgFn != null) {
+ colors = applyToRowBgFn(props.rowIdx);
+ } else if (cellType !== TableCellDisplayMode.Auto) {
+ const displayValue = field.display!(value); // this fires here to get colors, then again to get rendered value?
+ colors = getCellColors(theme, cellOptions, displayValue);
+ } else {
+ colors = {};
+ }
+
+ const cellStyle = getCellStyles(theme, field, _rowHeight, shouldWrap, shouldOverflow, colors);
+
+ return (
+ |
+ );
+ };
+
+ result.cellRootRenderers[displayName] = renderCellRoot;
+
+ // this fires second
+ const renderCellContent = (props: RenderCellProps): JSX.Element => {
+ const rowIdx = props.row.__index;
+ const value = props.row[props.column.key];
+
+ // TODO: defer until click?
+ const actions = getActions?.(frame, field, props.row.__index, replaceVariables);
+
+ return (
+ <>
+ {renderFieldCell({
+ actions,
+ cellOptions,
+ frame,
+ field,
+ height,
+ justifyContent,
+ rowIdx,
+ theme,
+ value,
+ width,
+ cellInspect,
+ showFilters,
+ })}
+ {showActions && (
+
+ )}
+ >
+ );
+ };
+
+ const column: TableColumn = {
field,
key: displayName,
name: displayName,
width,
headerCellClass,
- renderCell: (props: RenderCellProps): JSX.Element => {
- // TODO: once per row
- const height = typeof rowHeight === 'function' ? rowHeight(props.row) : rowHeight;
- // TODO: defer until click?
- const actions = getActions?.(frame, field, props.row.__index, replaceVariables);
-
- const rowIdx = props.row.__index;
- const value = props.row[displayName];
-
- return (
- <>
- {renderFieldCell({
- actions,
- cellOptions,
- frame,
- field,
- height,
- justifyContent,
- rowIdx,
- theme,
- value,
- width,
- cellInspect,
- showFilters,
- })}
- {showActions && (
-
- )}
- >
- );
- },
+ renderCell: renderCellContent,
renderHeaderCell: ({ column, sortDirection }): JSX.Element => (
{footerCalcs[i]};
},
};
+
+ result.columns.push(column);
});
- const result: TableColumn[] = columnsFromFields(visibleFields, widths);
+ return result;
+ };
+
+ const result = fromFields(visibleFields, widths);
// handle nested frames rendering from here.
if (!hasNestedFrames) {
@@ -356,13 +409,17 @@ export function TableNG(props: TableNGProps) {
}
const renderRow = renderRowFactory(firstNestedData.fields, panelContext, expandedRows, enableSharedCrosshair);
- const expandedColumns = columnsFromFields(
+ const { columns: nestedColumns, cellRootRenderers: nestedCellRootRenderers } = fromFields(
firstNestedData.fields,
computeColWidths(firstNestedData.fields, availableWidth)
);
+ const renderCellRoot: CellRootRenderer = (key, props) => nestedCellRootRenderers[props.column.key](key, props);
+
+ result.cellRootRenderers.expanded = (key, props) =>
| ;
+
// If we have nested frames, we need to add a column for the row expansion
- result.unshift({
+ result.columns.unshift({
key: 'expanded',
name: '',
field: {
@@ -372,16 +429,16 @@ export function TableNG(props: TableNGProps) {
values: [],
},
cellClass(row) {
- if (Number(row.__depth) !== 0) {
+ if (row.__depth !== 0) {
return styles.cellNested;
}
return;
},
colSpan(args) {
- return args.type === 'ROW' && Number(args.row.__depth) === 1 ? data.fields.length : 1;
+ return args.type === 'ROW' && args.row.__depth === 1 ? data.fields.length : 1;
},
renderCell: ({ row }) => {
- if (Number(row.__depth) === 0) {
+ if (row.__depth === 0) {
return (
{...commonDataGridProps}
className={cx(styles.grid, styles.gridNested)}
- columns={expandedColumns}
+ columns={nestedColumns}
rows={expandedRecords}
- renderers={{ renderRow, renderCell }}
+ renderers={{ renderRow, renderCell: renderCellRoot }}
/>
);
},
@@ -433,7 +490,6 @@ export function TableNG(props: TableNGProps) {
onCellFilterAdded,
panelContext,
replaceVariables,
- renderCell,
rows,
rowHeight,
setFilter,
@@ -443,6 +499,10 @@ export function TableNG(props: TableNGProps) {
theme,
visibleFields,
widths,
+ applyToRowBgFn,
+ columnTypes,
+ height,
+ textWraps,
]);
// invalidate columns on every structureRev change. this supports width editing in the fieldConfig.
@@ -454,6 +514,10 @@ export function TableNG(props: TableNGProps) {
const displayedEnd = pageRangeEnd;
const numRows = sortedRows.length;
+ const renderCellRoot: CellRootRenderer = (key, props) => {
+ return cellRootRenderers[props.column.key](key, props);
+ };
+
return (
<>
@@ -471,7 +535,7 @@ export function TableNG(props: TableNGProps) {
}
: null
}
- renderers={{ renderRow, renderCell }}
+ renderers={{ renderRow, renderCell: renderCellRoot }}
/>
{enablePagination && (
@@ -538,11 +602,11 @@ const renderRowFactory =
) =>
(key: React.Key, props: RenderRowProps): React.ReactNode => {
const { row } = props;
- const rowIdx = Number(row.__index);
+ const rowIdx = row.__index;
const isExpanded = !!expandedRows[rowIdx];
// Don't render non expanded child rows
- if (Number(row.__depth) === 1 && !isExpanded) {
+ if (row.__depth === 1 && !isExpanded) {
return null;
}
@@ -573,63 +637,6 @@ const renderRowFactory =
return
;
};
-/**
- * passed to the top-level `renderCell` prop on DataGrid. This applies all per-cell styles.
- */
-const renderCellFactory =
- (
- columnTypes: Record,
- applyToRowBgFn: ((rowIdx: number) => CellColors) | undefined,
- rowHeight: number | ((row: TableRow) => number),
- textWraps: Record,
- theme: GrafanaTheme2,
- visibleFieldsByDisplayName: Record
- ) =>
- (key: Key, props: CellRendererProps) => {
- const displayName = props.column.key;
- const field = visibleFieldsByDisplayName[displayName];
-
- // exit early if we fail to look up the field from the column key.
- if (!field) {
- return | ;
- }
-
- const cellOptions = getCellOptions(field);
- const cellType = cellOptions.type;
- const value = props.row[props.column.key];
-
- const colors: CellColors = (() => {
- if (applyToRowBgFn) {
- return applyToRowBgFn(props.rowIdx);
- }
- const displayValue = field.display?.(value);
- if (displayValue && cellOptions) {
- return getCellColors(theme, cellOptions, displayValue);
- }
- return {};
- })();
-
- const rh = typeof rowHeight === 'function' ? rowHeight(props.row) : rowHeight;
- const shouldOverflow = shouldTextOverflow(
- displayName,
- columnTypes,
- textWraps[getDisplayName(field)],
- field,
- cellType
- );
- const shouldWrap = textWraps[displayName] ?? false;
- const cellStyle = getCellStyles(theme, field, rh, shouldWrap, shouldOverflow, colors);
-
- return (
- |
- );
- };
-
const getGridStyles = (
theme: GrafanaTheme2,
{ enablePagination, noHeader }: { enablePagination?: boolean; noHeader?: boolean }
diff --git a/packages/grafana-ui/src/components/Table/TableNG/utils.ts b/packages/grafana-ui/src/components/Table/TableNG/utils.ts
index d65b1cd9ff5..79afab8b076 100644
--- a/packages/grafana-ui/src/components/Table/TableNG/utils.ts
+++ b/packages/grafana-ui/src/components/Table/TableNG/utils.ts
@@ -74,21 +74,14 @@ export function getDefaultRowHeight(theme: GrafanaTheme2, cellHeight?: TableCell
* Returns true if text overflow handling should be applied to the cell.
*/
export function shouldTextOverflow(
- key: string,
- columnTypes: ColumnTypes,
+ fieldType: FieldType,
+ cellType: TableCellDisplayMode,
textWrap: boolean,
- field: Field,
- cellType: TableCellDisplayMode
+ cellInspect: boolean
): boolean {
- const cellInspect = field.config?.custom?.inspect ?? false;
-
// Tech debt: Technically image cells are of type string, which is misleading (kinda?)
// so we need to ensure we don't apply overflow hover states fo type image
- if (textWrap || cellInspect || cellType === TableCellDisplayMode.Image || columnTypes[key] !== FieldType.string) {
- return false;
- }
-
- return true;
+ return fieldType === FieldType.string && cellType !== TableCellDisplayMode.Image && !textWrap && !cellInspect;
}
/**
diff --git a/packages/grafana-ui/src/components/Table/utils.ts b/packages/grafana-ui/src/components/Table/utils.ts
index 1f1f9f0c50c..f4c9b072b16 100644
--- a/packages/grafana-ui/src/components/Table/utils.ts
+++ b/packages/grafana-ui/src/components/Table/utils.ts
@@ -775,3 +775,21 @@ export const getDataLinksActionsTooltipUtils = (links: LinkModel[], actions?: Ac
return { shouldShowLink, hasMultipleLinksOrActions };
};
+
+const shouldTriggerTooltip = (event: React.MouseEvent): boolean => {
+ return event.target === event.currentTarget;
+};
+
+/**
+ * Creates an onClick handler for table cells that only triggers tooltip when clicking directly on the cell
+ * @param setTooltipCoords - function to set tooltip coordinates
+ * @returns onClick handler
+ */
+export const tooltipOnClickHandler = (setTooltipCoords: (coords: DataLinksActionsTooltipCoords) => void) => {
+ return (event: React.MouseEvent) => {
+ if (shouldTriggerTooltip(event)) {
+ const { clientX, clientY } = event;
+ setTooltipCoords({ clientX, clientY });
+ }
+ };
+};
diff --git a/pkg/apis/secret/v0alpha1/secure_value.go b/pkg/apis/secret/v0alpha1/secure_value.go
index 86212f39c02..26a68263568 100644
--- a/pkg/apis/secret/v0alpha1/secure_value.go
+++ b/pkg/apis/secret/v0alpha1/secure_value.go
@@ -59,7 +59,9 @@ type SecureValueSpec struct {
// The raw value is only valid for write. Read/List will always be empty.
// There is no support for mixing `value` and `ref`, you can't create a secret in a third-party keeper with a specified `ref`.
+ // Minimum and maximum lengths in bytes.
// +k8s:validation:minLength=1
+ // +k8s:validation:maxLength=24576
Value ExposedSecureValue `json:"value,omitempty"`
// When using a third-party keeper, the `ref` is used to reference a value inside the remote storage.
diff --git a/pkg/apis/secret/v0alpha1/zz_generated.openapi.go b/pkg/apis/secret/v0alpha1/zz_generated.openapi.go
index 836db012cb0..9bb5f4753e5 100644
--- a/pkg/apis/secret/v0alpha1/zz_generated.openapi.go
+++ b/pkg/apis/secret/v0alpha1/zz_generated.openapi.go
@@ -641,8 +641,9 @@ func schema_pkg_apis_secret_v0alpha1_SecureValueSpec(ref common.ReferenceCallbac
},
"value": {
SchemaProps: spec.SchemaProps{
- Description: "The raw value is only valid for write. Read/List will always be empty. There is no support for mixing `value` and `ref`, you can't create a secret in a third-party keeper with a specified `ref`.",
+ Description: "The raw value is only valid for write. Read/List will always be empty. There is no support for mixing `value` and `ref`, you can't create a secret in a third-party keeper with a specified `ref`. Minimum and maximum lengths in bytes.",
MinLength: ptr.To[int64](1),
+ MaxLength: ptr.To[int64](24576),
Type: []string{"string"},
Format: "",
},
diff --git a/pkg/modules/dependencies.go b/pkg/modules/dependencies.go
index e413f5248c4..3ffe045ce06 100644
--- a/pkg/modules/dependencies.go
+++ b/pkg/modules/dependencies.go
@@ -4,25 +4,25 @@ const (
// All includes all modules necessary for Grafana to run as a standalone server
All string = "all"
- Core string = "core"
- MemberlistKV string = "memberlistkv"
- GrafanaAPIServer string = "grafana-apiserver"
- StorageRing string = "storage-ring"
- Distributor string = "distributor"
- StorageServer string = "storage-server"
- ZanzanaServer string = "zanzana-server"
- InstrumentationServer string = "instrumentation-server"
- FrontendServer string = "frontend-server"
+ Core string = "core"
+ MemberlistKV string = "memberlistkv"
+ GrafanaAPIServer string = "grafana-apiserver"
+ SearchServerRing string = "search-server-ring"
+ SearchServerDistributor string = "search-server-distributor"
+ StorageServer string = "storage-server"
+ ZanzanaServer string = "zanzana-server"
+ InstrumentationServer string = "instrumentation-server"
+ FrontendServer string = "frontend-server"
)
var dependencyMap = map[string][]string{
- MemberlistKV: {InstrumentationServer},
- StorageRing: {InstrumentationServer, MemberlistKV},
- GrafanaAPIServer: {InstrumentationServer},
- StorageServer: {InstrumentationServer, StorageRing},
- ZanzanaServer: {InstrumentationServer},
- Distributor: {InstrumentationServer, MemberlistKV, StorageRing},
- Core: {},
- All: {Core},
- FrontendServer: {},
+ MemberlistKV: {InstrumentationServer},
+ SearchServerRing: {InstrumentationServer, MemberlistKV},
+ GrafanaAPIServer: {InstrumentationServer},
+ StorageServer: {InstrumentationServer, SearchServerRing},
+ ZanzanaServer: {InstrumentationServer},
+ SearchServerDistributor: {InstrumentationServer, MemberlistKV, SearchServerRing},
+ Core: {},
+ All: {Core},
+ FrontendServer: {},
}
diff --git a/pkg/plugins/repo/client.go b/pkg/plugins/repo/client.go
index 1c4c6496ece..698b61e186d 100644
--- a/pkg/plugins/repo/client.go
+++ b/pkg/plugins/repo/client.go
@@ -100,7 +100,7 @@ func (c *Client) SendReq(ctx context.Context, url *url.URL, compatOpts CompatOpt
return io.ReadAll(bodyReader)
}
-func (c *Client) downloadFile(ctx context.Context, tmpFile *os.File, pluginURL, checksum string, compatOpts CompatOpts) (err error) {
+func (c *Client) downloadFile(ctx context.Context, tmpFile *os.File, pluginURL, expectedChecksum string, compatOpts CompatOpts) (err error) {
// Try handling URL as a local file path first
if _, err := os.Stat(pluginURL); err == nil {
// TODO re-verify
@@ -136,7 +136,7 @@ func (c *Client) downloadFile(ctx context.Context, tmpFile *os.File, pluginURL,
if err != nil {
return
}
- err = c.downloadFile(ctx, tmpFile, pluginURL, checksum, compatOpts)
+ err = c.downloadFile(ctx, tmpFile, pluginURL, expectedChecksum, compatOpts)
} else {
c.retryCount = 0
failure := fmt.Sprintf("%v", r)
@@ -169,7 +169,7 @@ func (c *Client) downloadFile(ctx context.Context, tmpFile *os.File, pluginURL,
if c.retryCount < 3 {
c.retryCount++
c.log.Debug("Failed downloading. Will retry.")
- err = c.downloadFile(ctx, tmpFile, pluginURL, checksum, compatOpts)
+ err = c.downloadFile(ctx, tmpFile, pluginURL, expectedChecksum, compatOpts)
}
return err
}
@@ -187,8 +187,9 @@ func (c *Client) downloadFile(ctx context.Context, tmpFile *os.File, pluginURL,
if err = w.Flush(); err != nil {
return fmt.Errorf("failed to write to %q: %w", tmpFile.Name(), err)
}
- if len(checksum) > 0 && checksum != fmt.Sprintf("%x", h.Sum(nil)) {
- return ErrChecksumMismatch(pluginURL)
+ computedChecksum := fmt.Sprintf("%x", h.Sum(nil))
+ if len(expectedChecksum) > 0 && expectedChecksum != computedChecksum {
+ return ErrChecksumMismatch(pluginURL, expectedChecksum, computedChecksum)
}
c.retryCount = 0
diff --git a/pkg/plugins/repo/errors.go b/pkg/plugins/repo/errors.go
index c275ce43afc..f25474345b1 100644
--- a/pkg/plugins/repo/errors.go
+++ b/pkg/plugins/repo/errors.go
@@ -60,7 +60,7 @@ var (
ErrArcNotFoundBase = errutil.NotFound("plugin.archNotFound").
MustTemplate(ErrArcNotFoundMsg, errutil.WithPublic(ErrArcNotFoundMsg))
- ErrChecksumMismatchMsg = "expected SHA256 checksum does not match the downloaded archive ({{.Public.ArchiveURL}}) - please contact security@grafana.com"
+ ErrChecksumMismatchMsg = "expected SHA256 checksum ({{.Public.ExpectedSHA256}}) does not match the downloaded archive ({{.Public.ArchiveURL}}) computed SHA256 checksum ({{.Public.ComputedSHA256}}) - please contact security@grafana.com"
ErrChecksumMismatchBase = errutil.UnprocessableEntity("plugin.checksumMismatch").
MustTemplate(ErrChecksumMismatchMsg, errutil.WithPublic(ErrChecksumMismatchMsg))
@@ -85,8 +85,8 @@ func ErrArcNotFound(pluginID, systemInfo string) error {
return ErrArcNotFoundBase.Build(errutil.TemplateData{Public: map[string]any{"PluginID": pluginID, "SysInfo": systemInfo}})
}
-func ErrChecksumMismatch(archiveURL string) error {
- return ErrChecksumMismatchBase.Build(errutil.TemplateData{Public: map[string]any{"ArchiveURL": archiveURL}})
+func ErrChecksumMismatch(archiveURL, expectedSHA256, computedSHA256 string) error {
+ return ErrChecksumMismatchBase.Build(errutil.TemplateData{Public: map[string]any{"ArchiveURL": archiveURL, "ExpectedSHA256": expectedSHA256, "ComputedSHA256": computedSHA256}})
}
func ErrCorePlugin(pluginID string) error {
diff --git a/pkg/plugins/repo/errors_test.go b/pkg/plugins/repo/errors_test.go
index 7e10ed587b8..59f4b7446df 100644
--- a/pkg/plugins/repo/errors_test.go
+++ b/pkg/plugins/repo/errors_test.go
@@ -49,11 +49,13 @@ func TestErrorTemplates(t *testing.T) {
require.Equal(t, "plugin.archNotFound", base.Public().MessageID)
require.Equal(t, "grafana-test-app is not compatible with your system architecture: darwin-amd64", base.Public().Message)
- err = ErrChecksumMismatch("http://localhost:6481/grafana-test-app/versions/1.0.0/download")
+ expectedChecksum := "abcdef1234567890"
+ computedChecksum := "abcdef0987654321"
+ err = ErrChecksumMismatch("http://localhost:6481/grafana-test-app/versions/1.0.0/download", expectedChecksum, computedChecksum)
require.True(t, errors.As(err, base))
require.Equal(t, http.StatusUnprocessableEntity, base.Public().StatusCode)
require.Equal(t, "plugin.checksumMismatch", base.Public().MessageID)
- require.Equal(t, "expected SHA256 checksum does not match the downloaded archive (http://localhost:6481/grafana-test-app/versions/1.0.0/download) - please contact security@grafana.com", base.Public().Message)
+ require.Equal(t, "expected SHA256 checksum (abcdef1234567890) does not match the downloaded archive (http://localhost:6481/grafana-test-app/versions/1.0.0/download) computed SHA256 checksum (abcdef0987654321) - please contact security@grafana.com", base.Public().Message)
err = ErrCorePlugin("grafana-test-app")
require.True(t, errors.As(err, base))
diff --git a/pkg/registry/apis/datasource/converter.go b/pkg/registry/apis/datasource/converter.go
index 8cd6ce40108..341dbf5f813 100644
--- a/pkg/registry/apis/datasource/converter.go
+++ b/pkg/registry/apis/datasource/converter.go
@@ -6,6 +6,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/components/simplejson"
@@ -91,11 +92,16 @@ func (r *converter) toAddCommand(ds *v0alpha1.GenericDataSource) (*datasources.A
if r.group != "" && !strings.HasPrefix(ds.APIVersion, r.group) {
return nil, fmt.Errorf("expecting APIGroup: %s", r.group)
}
+ info, err := types.ParseNamespace(ds.Namespace)
+ if err != nil {
+ return nil, err
+ }
cmd := &datasources.AddDataSourceCommand{
- Name: ds.Spec.Title,
- UID: ds.Name,
- Type: r.dstype,
+ Name: ds.Spec.Title,
+ UID: ds.Name,
+ OrgID: info.OrgID,
+ Type: r.dstype,
Access: datasources.DsAccess(ds.Spec.Access),
URL: ds.Spec.URL,
@@ -121,11 +127,16 @@ func (r *converter) toUpdateCommand(ds *v0alpha1.GenericDataSource) (*datasource
if r.group != "" && !strings.HasPrefix(ds.APIVersion, r.group) {
return nil, fmt.Errorf("expecting APIGroup: %s", r.group)
}
+ info, err := types.ParseNamespace(ds.Namespace)
+ if err != nil {
+ return nil, err
+ }
cmd := &datasources.UpdateDataSourceCommand{
- Name: ds.Spec.Title,
- UID: ds.Name,
- Type: r.dstype,
+ Name: ds.Spec.Title,
+ UID: ds.Name,
+ OrgID: info.OrgID,
+ Type: r.dstype,
Access: datasources.DsAccess(ds.Spec.Access),
URL: ds.Spec.URL,
@@ -136,15 +147,15 @@ func (r *converter) toUpdateCommand(ds *v0alpha1.GenericDataSource) (*datasource
WithCredentials: ds.Spec.WithCredentials,
IsDefault: ds.Spec.IsDefault,
ReadOnly: ds.Spec.ReadOnly,
+
+ // The only field different than add
+ Version: int(ds.Generation),
}
if len(ds.Spec.JsonData.Object) > 0 {
cmd.JsonData = simplejson.NewFromAny(ds.Spec.JsonData.Object)
}
cmd.SecureJsonData = toSecureJsonData(ds)
-
- // The only thing differnet from the add command???
- cmd.Version = int(ds.Generation)
return cmd, nil
}
diff --git a/pkg/registry/apis/secret/contracts/secure_value.go b/pkg/registry/apis/secret/contracts/secure_value.go
index 25508f16569..ca3f66f991d 100644
--- a/pkg/registry/apis/secret/contracts/secure_value.go
+++ b/pkg/registry/apis/secret/contracts/secure_value.go
@@ -8,6 +8,9 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/secret/xkube"
)
+// The maximum size of a secure value in bytes when written as raw input.
+const SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES = 24576 // 24 KiB
+
type DecryptSecureValue struct {
Keeper *string
Ref string
diff --git a/pkg/registry/apis/secret/reststorage/secure_value_rest.go b/pkg/registry/apis/secret/reststorage/secure_value_rest.go
index 824a77869f7..7ea856509ea 100644
--- a/pkg/registry/apis/secret/reststorage/secure_value_rest.go
+++ b/pkg/registry/apis/secret/reststorage/secure_value_rest.go
@@ -13,6 +13,7 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/endpoints/request"
@@ -245,6 +246,13 @@ func ValidateSecureValue(sv, oldSv *secretv0alpha1.SecureValue, operation admiss
}
// General validations.
+ if len(sv.Spec.Value) > contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES {
+ errs = append(
+ errs,
+ field.TooLong(field.NewPath("spec", "value"), len(sv.Spec.Value), contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES),
+ )
+ }
+
if errs := validateDecrypters(sv.Spec.Decrypters, decryptersAllowList); len(errs) > 0 {
return errs
}
@@ -301,7 +309,7 @@ func validateSecureValueUpdate(sv, oldSv *secretv0alpha1.SecureValue) field.Erro
return errs
}
-// validateDecrypters validates that (if populated) the `decrypters` must match "actor_{name}" and must be unique.
+// validateDecrypters validates that (if populated) the `decrypters` must be unique.
func validateDecrypters(decrypters []string, decryptersAllowList map[string]struct{}) field.ErrorList {
errs := make(field.ErrorList, 0)
@@ -319,8 +327,17 @@ func validateDecrypters(decrypters []string, decryptersAllowList map[string]stru
decrypterNames := make(map[string]struct{}, 0)
for i, decrypter := range decrypters {
+ decrypter = strings.TrimSpace(decrypter)
+ if decrypter == "" {
+ errs = append(
+ errs,
+ field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, "decrypters cannot be empty if specified"),
+ )
+
+ continue
+ }
+
// Allow List: decrypters must match exactly and be in the allowed list to be able to decrypt.
- // This means an allow list item should have the format "actor_{name}" and not just "{name}".
if len(decryptersAllowList) > 0 {
if _, exists := decryptersAllowList[decrypter]; !exists {
errs = append(
@@ -334,17 +351,19 @@ func validateDecrypters(decrypters []string, decryptersAllowList map[string]stru
continue
}
- actor, name, found := strings.Cut(strings.TrimSpace(decrypter), "_")
- if !found || actor != "actor" || name == "" {
- errs = append(
- errs,
- field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, "a decrypter must have the format `actor_{name}`"),
- )
+ // Use the same validation as labels for the decrypters.
+ if verrs := validation.IsValidLabelValue(decrypter); len(verrs) > 0 {
+ for _, verr := range verrs {
+ errs = append(
+ errs,
+ field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, verr),
+ )
+ }
continue
}
- if _, exists := decrypterNames[name]; exists {
+ if _, exists := decrypterNames[decrypter]; exists {
errs = append(
errs,
field.Invalid(field.NewPath("spec", "decrypters", "["+strconv.Itoa(i)+"]"), decrypter, "decrypters must be unique"),
@@ -353,7 +372,7 @@ func validateDecrypters(decrypters []string, decryptersAllowList map[string]stru
continue
}
- decrypterNames[name] = struct{}{}
+ decrypterNames[decrypter] = struct{}{}
}
return errs
diff --git a/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go b/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go
index 113faf6f0aa..1656ca7a1fc 100644
--- a/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go
+++ b/pkg/registry/apis/secret/reststorage/secure_value_rest_test.go
@@ -4,12 +4,14 @@ import (
"fmt"
"maps"
"slices"
+ "strings"
"testing"
"github.com/stretchr/testify/require"
"k8s.io/apiserver/pkg/admission"
secretv0alpha1 "github.com/grafana/grafana/pkg/apis/secret/v0alpha1"
+ "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
)
func TestValidateSecureValue(t *testing.T) {
@@ -20,7 +22,7 @@ func TestValidateSecureValue(t *testing.T) {
Description: "description",
Value: "value",
Keeper: &keeper,
- Decrypters: []string{"actor_app1", "actor_app2"},
+ Decrypters: []string{"app1", "app2"},
},
}
@@ -50,6 +52,16 @@ func TestValidateSecureValue(t *testing.T) {
require.Len(t, errs, 1)
require.Equal(t, "spec", errs[0].Field)
})
+
+ t.Run("`value` cannot exceed 24576 bytes", func(t *testing.T) {
+ sv := validSecureValue.DeepCopy()
+ sv.Spec.Value = secretv0alpha1.NewExposedSecureValue(strings.Repeat("a", contracts.SECURE_VALUE_RAW_INPUT_MAX_SIZE_BYTES+1))
+ sv.Spec.Ref = nil
+
+ errs := ValidateSecureValue(sv, nil, admission.Create, nil)
+ require.Len(t, errs, 1)
+ require.Equal(t, "spec.value", errs[0].Field)
+ })
})
t.Run("when updating a securevalue", func(t *testing.T) {
@@ -175,8 +187,8 @@ func TestValidateSecureValue(t *testing.T) {
Description: "description", Ref: &ref,
Decrypters: []string{
- "actor_app1",
- "actor_app1",
+ "app1",
+ "app1",
},
},
}
@@ -186,33 +198,8 @@ func TestValidateSecureValue(t *testing.T) {
require.Equal(t, "spec.decrypters.[1]", errs[0].Field)
})
- t.Run("`decrypters` must match the expected format", func(t *testing.T) {
- ref := "ref"
- sv := &secretv0alpha1.SecureValue{
- Spec: secretv0alpha1.SecureValueSpec{
- Description: "description", Ref: &ref,
-
- Decrypters: []string{
- "app1",
- "_app1",
- "actr_app1",
- "actor_ ",
- "actor_",
- },
- },
- }
-
- errs := ValidateSecureValue(sv, nil, admission.Create, nil)
- require.Len(t, errs, len(sv.Spec.Decrypters))
-
- for i, err := range errs {
- require.Equal(t, fmt.Sprintf("spec.decrypters.[%d]", i), err.Field)
- require.Contains(t, err.Error(), "a decrypter must have the format `actor_{name}`")
- }
- })
-
t.Run("when set, the `decrypters` must be one of the allowed in the allow list", func(t *testing.T) {
- allowList := map[string]struct{}{"actor_app1": {}, "actor_app2": {}}
+ allowList := map[string]struct{}{"app1": {}, "app2": {}}
decrypters := slices.Collect(maps.Keys(allowList))
t.Run("no matches, returns an error", func(t *testing.T) {
@@ -221,7 +208,7 @@ func TestValidateSecureValue(t *testing.T) {
Spec: secretv0alpha1.SecureValueSpec{
Description: "description", Ref: &ref,
- Decrypters: []string{"actor_app3"},
+ Decrypters: []string{"app3"},
},
}
@@ -272,10 +259,37 @@ func TestValidateSecureValue(t *testing.T) {
})
})
+ t.Run("`decrypters` must be a valid label value", func(t *testing.T) {
+ decrypters := []string{
+ "", // invalid
+ "is/this/valid", // invalid
+ "is this valid", // invalid
+ "is.this.valid",
+ "is-this-valid",
+ "is_this_valid",
+ "0isthisvalid9",
+ "isthisvalid9",
+ "0isthisvalid",
+ "isthisvalid",
+ }
+
+ ref := "ref"
+ sv := &secretv0alpha1.SecureValue{
+ Spec: secretv0alpha1.SecureValueSpec{
+ Description: "description", Ref: &ref,
+
+ Decrypters: decrypters,
+ },
+ }
+
+ errs := ValidateSecureValue(sv, nil, admission.Create, nil)
+ require.Len(t, errs, 3)
+ })
+
t.Run("`decrypters` cannot have more than 64 items", func(t *testing.T) {
decrypters := make([]string, 0, 64+1)
for i := 0; i < 64+1; i++ {
- decrypters = append(decrypters, fmt.Sprintf("actor_app%d", i))
+ decrypters = append(decrypters, fmt.Sprintf("app%d", i))
}
ref := "ref"
diff --git a/pkg/server/module_server.go b/pkg/server/module_server.go
index 3c141d2e18d..979e993eb7a 100644
--- a/pkg/server/module_server.go
+++ b/pkg/server/module_server.go
@@ -53,7 +53,16 @@ func NewModule(opts Options,
return s, nil
}
-func newModuleServer(opts Options, apiOpts api.ServerOptions, features featuremgmt.FeatureToggles, cfg *setting.Cfg, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics, reg prometheus.Registerer, promGatherer prometheus.Gatherer, license licensing.Licensing) (*ModuleServer, error) {
+func newModuleServer(opts Options,
+ apiOpts api.ServerOptions,
+ features featuremgmt.FeatureToggles,
+ cfg *setting.Cfg,
+ storageMetrics *resource.StorageMetrics,
+ indexMetrics *resource.BleveIndexMetrics,
+ reg prometheus.Registerer,
+ promGatherer prometheus.Gatherer,
+ license licensing.Licensing,
+) (*ModuleServer, error) {
rootCtx, shutdownFn := context.WithCancel(context.Background())
s := &ModuleServer{
@@ -107,10 +116,10 @@ type ModuleServer struct {
promGatherer prometheus.Gatherer
registerer prometheus.Registerer
- MemberlistKVConfig kv.Config
- httpServerRouter *mux.Router
- storageRing *ring.Ring
- storageRingClientPool *ringclient.Pool
+ MemberlistKVConfig kv.Config
+ httpServerRouter *mux.Router
+ searchServerRing *ring.Ring
+ searchServerRingClientPool *ringclient.Pool
}
// init initializes the server and its services.
@@ -153,8 +162,8 @@ func (s *ModuleServer) Run() error {
})
m.RegisterModule(modules.MemberlistKV, s.initMemberlistKV)
- m.RegisterModule(modules.StorageRing, s.initRing)
- m.RegisterModule(modules.Distributor, s.initDistributor)
+ m.RegisterModule(modules.SearchServerRing, s.initSearchServerRing)
+ m.RegisterModule(modules.SearchServerDistributor, s.initSearchServerDistributor)
m.RegisterModule(modules.Core, func() (services.Service, error) {
return NewService(s.cfg, s.opts, s.apiOpts)
@@ -174,7 +183,7 @@ func (s *ModuleServer) Run() error {
if err != nil {
return nil, err
}
- return sql.ProvideUnifiedStorageGrpcService(s.cfg, s.features, nil, s.log, s.registerer, docBuilders, s.storageMetrics, s.indexMetrics, s.storageRing, s.MemberlistKVConfig)
+ return sql.ProvideUnifiedStorageGrpcService(s.cfg, s.features, nil, s.log, s.registerer, docBuilders, s.storageMetrics, s.indexMetrics, s.searchServerRing, s.MemberlistKVConfig)
})
m.RegisterModule(modules.ZanzanaServer, func() (services.Service, error) {
diff --git a/pkg/server/ring.go b/pkg/server/ring.go
index 1499702a81a..cd89f719f8a 100644
--- a/pkg/server/ring.go
+++ b/pkg/server/ring.go
@@ -25,7 +25,7 @@ import (
var metricsPrefix = resource.RingName + "_"
-func (ms *ModuleServer) initRing() (services.Service, error) {
+func (ms *ModuleServer) initSearchServerRing() (services.Service, error) {
if !ms.cfg.EnableSharding {
return nil, nil
}
@@ -48,7 +48,7 @@ func (ms *ModuleServer) initRing() (services.Service, error) {
return nil, fmt.Errorf("failed to create KV store client: %s", err)
}
- storageRing, err := ring.NewWithStoreClientAndStrategy(
+ searchServerRing, err := ring.NewWithStoreClientAndStrategy(
toRingConfig(ms.cfg, ms.MemberlistKVConfig),
resource.RingName,
resource.RingKey,
@@ -58,11 +58,11 @@ func (ms *ModuleServer) initRing() (services.Service, error) {
logger,
)
if err != nil {
- return nil, fmt.Errorf("failed to initialize storage-ring ring: %s", err)
+ return nil, fmt.Errorf("failed to initialize index-server-ring ring: %s", err)
}
startFn := func(ctx context.Context) error {
- err = storageRing.StartAsync(ctx)
+ err = searchServerRing.StartAsync(ctx)
if err != nil {
return fmt.Errorf("failed to start the ring: %s", err)
}
@@ -74,10 +74,10 @@ func (ms *ModuleServer) initRing() (services.Service, error) {
return nil
}
- ms.storageRing = storageRing
- ms.storageRingClientPool = pool
+ ms.searchServerRing = searchServerRing
+ ms.searchServerRingClientPool = pool
- ms.httpServerRouter.Path("/ring").Methods("GET", "POST").Handler(storageRing)
+ ms.httpServerRouter.Path("/ring").Methods("GET", "POST").Handler(searchServerRing)
svc := services.NewIdleService(startFn, nil)
diff --git a/pkg/server/distributor.go b/pkg/server/search_server_distributor.go
similarity index 63%
rename from pkg/server/distributor.go
rename to pkg/server/search_server_distributor.go
index 79306666822..41eaabd8b28 100644
--- a/pkg/server/distributor.go
+++ b/pkg/server/search_server_distributor.go
@@ -10,18 +10,18 @@ import (
"go.opentelemetry.io/otel"
)
-func (ms *ModuleServer) initDistributor() (services.Service, error) {
+func (ms *ModuleServer) initSearchServerDistributor() (services.Service, error) {
var (
distributor = &distributorService{}
- tracer = otel.Tracer("unified-storage-distributor")
+ tracer = otel.Tracer("index-server-distributor")
err error
)
- distributor.grpcHandler, err = resource.ProvideDistributorServer(ms.cfg, ms.features, ms.registerer, tracer, ms.storageRing, ms.storageRingClientPool)
+ distributor.grpcHandler, err = resource.ProvideSearchDistributorServer(ms.cfg, ms.features, ms.registerer, tracer, ms.searchServerRing, ms.searchServerRingClientPool)
if err != nil {
return nil, err
}
- return services.NewBasicService(nil, distributor.running, nil).WithName(modules.Distributor), nil
+ return services.NewBasicService(nil, distributor.running, nil).WithName(modules.SearchServerDistributor), nil
}
type distributorService struct {
diff --git a/pkg/server/distributor_test.go b/pkg/server/search_server_distributor_test.go
similarity index 97%
rename from pkg/server/distributor_test.go
rename to pkg/server/search_server_distributor_test.go
index 6468d4f0f3e..3062b1efe8d 100644
--- a/pkg/server/distributor_test.go
+++ b/pkg/server/search_server_distributor_test.go
@@ -273,7 +273,7 @@ func initDistributorServerForTest(t *testing.T, memberlistPort int) testModuleSe
cfg.MemberlistJoinMember = "127.0.0.1:" + strconv.Itoa(memberlistPort)
cfg.MemberlistAdvertiseAddr = "127.0.0.1"
cfg.MemberlistAdvertisePort = memberlistPort
- cfg.Target = []string{modules.Distributor}
+ cfg.Target = []string{modules.SearchServerDistributor}
cfg.InstanceID = "distributor" // does nothing for the distributor but may be useful to debug tests
conn, err := grpc.NewClient(cfg.GRPCServer.Address,
@@ -352,7 +352,18 @@ func createBaselineServer(t *testing.T, dbType, dbConnStr string, testNamespaces
require.NoError(t, err)
searchOpts, err := search.NewSearchOptions(features, cfg, tracer, docBuilders, nil)
require.NoError(t, err)
- server, err := sql.NewResourceServer(nil, cfg, tracer, nil, nil, searchOpts, nil, nil, features)
+ server, err := sql.NewResourceServer(sql.ServerOptions{
+ DB: nil,
+ Cfg: cfg,
+ Tracer: tracer,
+ Reg: nil,
+ AccessClient: nil,
+ SearchOptions: searchOpts,
+ StorageMetrics: nil,
+ IndexMetrics: nil,
+ Features: features,
+ QOSQueue: nil,
+ })
require.NoError(t, err)
testUserA := &identity.StaticRequester{
diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go
new file mode 100644
index 00000000000..efa495e511f
--- /dev/null
+++ b/pkg/server/wire_gen.go
@@ -0,0 +1,1461 @@
+// Code generated by Wire. DO NOT EDIT.
+
+//go:generate go run ./pkg/build/wire/cmd/wire/main.go gen -tags "oss"
+//go:build !wireinject && !enterprise && !pro
+
+package server
+
+import (
+ "github.com/google/wire"
+ httpclient2 "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
+ "github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry"
+ "github.com/grafana/grafana/pkg/api"
+ "github.com/grafana/grafana/pkg/api/avatar"
+ "github.com/grafana/grafana/pkg/api/routing"
+ "github.com/grafana/grafana/pkg/bus"
+ "github.com/grafana/grafana/pkg/expr"
+ "github.com/grafana/grafana/pkg/infra/db"
+ "github.com/grafana/grafana/pkg/infra/httpclient"
+ "github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider"
+ "github.com/grafana/grafana/pkg/infra/kvstore"
+ "github.com/grafana/grafana/pkg/infra/localcache"
+ "github.com/grafana/grafana/pkg/infra/log/slogadapter"
+ "github.com/grafana/grafana/pkg/infra/metrics"
+ "github.com/grafana/grafana/pkg/infra/remotecache"
+ "github.com/grafana/grafana/pkg/infra/serverlock"
+ "github.com/grafana/grafana/pkg/infra/tracing"
+ "github.com/grafana/grafana/pkg/infra/usagestats"
+ "github.com/grafana/grafana/pkg/infra/usagestats/service"
+ "github.com/grafana/grafana/pkg/infra/usagestats/statscollector"
+ validator2 "github.com/grafana/grafana/pkg/infra/usagestats/validator"
+ "github.com/grafana/grafana/pkg/login/social"
+ "github.com/grafana/grafana/pkg/login/social/connectors"
+ "github.com/grafana/grafana/pkg/login/social/socialimpl"
+ "github.com/grafana/grafana/pkg/middleware/csrf"
+ "github.com/grafana/grafana/pkg/middleware/loggermw"
+ "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
+ provider2 "github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
+ manager3 "github.com/grafana/grafana/pkg/plugins/manager"
+ "github.com/grafana/grafana/pkg/plugins/manager/filestore"
+ "github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath"
+ "github.com/grafana/grafana/pkg/plugins/manager/process"
+ "github.com/grafana/grafana/pkg/plugins/manager/registry"
+ "github.com/grafana/grafana/pkg/plugins/manager/signature"
+ "github.com/grafana/grafana/pkg/plugins/manager/sources"
+ "github.com/grafana/grafana/pkg/plugins/pluginscdn"
+ "github.com/grafana/grafana/pkg/plugins/repo"
+ "github.com/grafana/grafana/pkg/registry/apis"
+ "github.com/grafana/grafana/pkg/registry/apis/dashboard"
+ "github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
+ "github.com/grafana/grafana/pkg/registry/apis/dashboardsnapshot"
+ "github.com/grafana/grafana/pkg/registry/apis/datasource"
+ "github.com/grafana/grafana/pkg/registry/apis/featuretoggle"
+ "github.com/grafana/grafana/pkg/registry/apis/folders"
+ "github.com/grafana/grafana/pkg/registry/apis/iam"
+ "github.com/grafana/grafana/pkg/registry/apis/iam/noopstorage"
+ "github.com/grafana/grafana/pkg/registry/apis/ofrep"
+ provisioning2 "github.com/grafana/grafana/pkg/registry/apis/provisioning"
+ "github.com/grafana/grafana/pkg/registry/apis/provisioning/repository/github"
+ "github.com/grafana/grafana/pkg/registry/apis/provisioning/webhooks"
+ query2 "github.com/grafana/grafana/pkg/registry/apis/query"
+ "github.com/grafana/grafana/pkg/registry/apis/secret"
+ "github.com/grafana/grafana/pkg/registry/apis/secret/contracts"
+ "github.com/grafana/grafana/pkg/registry/apis/secret/decrypt"
+ "github.com/grafana/grafana/pkg/registry/apis/userstorage"
+ "github.com/grafana/grafana/pkg/registry/apps"
+ advisor2 "github.com/grafana/grafana/pkg/registry/apps/advisor"
+ notifications2 "github.com/grafana/grafana/pkg/registry/apps/alerting/notifications"
+ "github.com/grafana/grafana/pkg/registry/apps/investigations"
+ "github.com/grafana/grafana/pkg/registry/apps/playlist"
+ "github.com/grafana/grafana/pkg/registry/backgroundsvcs"
+ "github.com/grafana/grafana/pkg/registry/usagestatssvcs"
+ "github.com/grafana/grafana/pkg/services/accesscontrol"
+ "github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
+ dualwrite2 "github.com/grafana/grafana/pkg/services/accesscontrol/dualwrite"
+ "github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
+ "github.com/grafana/grafana/pkg/services/accesscontrol/permreg"
+ "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
+ "github.com/grafana/grafana/pkg/services/annotations"
+ "github.com/grafana/grafana/pkg/services/annotations/annotationsimpl"
+ "github.com/grafana/grafana/pkg/services/anonymous/anonimpl"
+ "github.com/grafana/grafana/pkg/services/anonymous/anonimpl/anonstore"
+ "github.com/grafana/grafana/pkg/services/anonymous/validator"
+ "github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
+ "github.com/grafana/grafana/pkg/services/apiserver"
+ "github.com/grafana/grafana/pkg/services/apiserver/aggregatorrunner"
+ "github.com/grafana/grafana/pkg/services/apiserver/builder"
+ "github.com/grafana/grafana/pkg/services/apiserver/standalone"
+ "github.com/grafana/grafana/pkg/services/auth"
+ "github.com/grafana/grafana/pkg/services/auth/authimpl"
+ "github.com/grafana/grafana/pkg/services/auth/idimpl"
+ "github.com/grafana/grafana/pkg/services/auth/jwt"
+ "github.com/grafana/grafana/pkg/services/authn/authnimpl"
+ "github.com/grafana/grafana/pkg/services/authz"
+ "github.com/grafana/grafana/pkg/services/caching"
+ "github.com/grafana/grafana/pkg/services/cleanup"
+ "github.com/grafana/grafana/pkg/services/cloudmigration/cloudmigrationimpl"
+ "github.com/grafana/grafana/pkg/services/contexthandler"
+ "github.com/grafana/grafana/pkg/services/correlations"
+ "github.com/grafana/grafana/pkg/services/dashboardimport"
+ service9 "github.com/grafana/grafana/pkg/services/dashboardimport/service"
+ dashboards2 "github.com/grafana/grafana/pkg/services/dashboards"
+ database2 "github.com/grafana/grafana/pkg/services/dashboards/database"
+ service5 "github.com/grafana/grafana/pkg/services/dashboards/service"
+ "github.com/grafana/grafana/pkg/services/dashboardsnapshots"
+ database4 "github.com/grafana/grafana/pkg/services/dashboardsnapshots/database"
+ service8 "github.com/grafana/grafana/pkg/services/dashboardsnapshots/service"
+ "github.com/grafana/grafana/pkg/services/dashboardversion/dashverimpl"
+ "github.com/grafana/grafana/pkg/services/datasourceproxy"
+ "github.com/grafana/grafana/pkg/services/datasources"
+ "github.com/grafana/grafana/pkg/services/datasources/guardian"
+ service7 "github.com/grafana/grafana/pkg/services/datasources/service"
+ "github.com/grafana/grafana/pkg/services/encryption"
+ "github.com/grafana/grafana/pkg/services/encryption/provider"
+ service2 "github.com/grafana/grafana/pkg/services/encryption/service"
+ "github.com/grafana/grafana/pkg/services/extsvcauth"
+ registry2 "github.com/grafana/grafana/pkg/services/extsvcauth/registry"
+ "github.com/grafana/grafana/pkg/services/featuremgmt"
+ "github.com/grafana/grafana/pkg/services/folder"
+ "github.com/grafana/grafana/pkg/services/folder/folderimpl"
+ "github.com/grafana/grafana/pkg/services/grpcserver"
+ "github.com/grafana/grafana/pkg/services/grpcserver/context"
+ "github.com/grafana/grafana/pkg/services/grpcserver/interceptors"
+ "github.com/grafana/grafana/pkg/services/hooks"
+ "github.com/grafana/grafana/pkg/services/kmsproviders/osskmsproviders"
+ "github.com/grafana/grafana/pkg/services/ldap"
+ api4 "github.com/grafana/grafana/pkg/services/ldap/api"
+ service10 "github.com/grafana/grafana/pkg/services/ldap/service"
+ "github.com/grafana/grafana/pkg/services/libraryelements"
+ "github.com/grafana/grafana/pkg/services/librarypanels"
+ "github.com/grafana/grafana/pkg/services/licensing"
+ "github.com/grafana/grafana/pkg/services/live"
+ "github.com/grafana/grafana/pkg/services/live/pushhttp"
+ "github.com/grafana/grafana/pkg/services/login"
+ "github.com/grafana/grafana/pkg/services/login/authinfoimpl"
+ "github.com/grafana/grafana/pkg/services/loginattempt"
+ "github.com/grafana/grafana/pkg/services/loginattempt/loginattemptimpl"
+ "github.com/grafana/grafana/pkg/services/navtree/navtreeimpl"
+ "github.com/grafana/grafana/pkg/services/ngalert"
+ "github.com/grafana/grafana/pkg/services/ngalert/image"
+ metrics2 "github.com/grafana/grafana/pkg/services/ngalert/metrics"
+ store2 "github.com/grafana/grafana/pkg/services/ngalert/store"
+ "github.com/grafana/grafana/pkg/services/notifications"
+ "github.com/grafana/grafana/pkg/services/oauthtoken"
+ "github.com/grafana/grafana/pkg/services/oauthtoken/oauthtokentest"
+ "github.com/grafana/grafana/pkg/services/org/orgimpl"
+ "github.com/grafana/grafana/pkg/services/playlist/playlistimpl"
+ "github.com/grafana/grafana/pkg/services/plugindashboards"
+ service6 "github.com/grafana/grafana/pkg/services/plugindashboards/service"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/advisor"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/angulardetectorsprovider"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/angularinspector"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/angularpatternsstore"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/dashboards"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/keyretriever"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/keyretriever/dynamic"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/keystore"
+ licensing2 "github.com/grafana/grafana/pkg/services/pluginsintegration/licensing"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/loader"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig"
+ "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"
+ service4 "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings/service"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/renderer"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/sandbox"
+ "github.com/grafana/grafana/pkg/services/pluginsintegration/serviceregistration"
+ "github.com/grafana/grafana/pkg/services/preference/prefimpl"
+ "github.com/grafana/grafana/pkg/services/provisioning"
+ "github.com/grafana/grafana/pkg/services/publicdashboards"
+ api2 "github.com/grafana/grafana/pkg/services/publicdashboards/api"
+ database3 "github.com/grafana/grafana/pkg/services/publicdashboards/database"
+ "github.com/grafana/grafana/pkg/services/publicdashboards/metric"
+ service3 "github.com/grafana/grafana/pkg/services/publicdashboards/service"
+ "github.com/grafana/grafana/pkg/services/query"
+ "github.com/grafana/grafana/pkg/services/queryhistory"
+ "github.com/grafana/grafana/pkg/services/quota/quotaimpl"
+ "github.com/grafana/grafana/pkg/services/rendering"
+ search2 "github.com/grafana/grafana/pkg/services/search"
+ "github.com/grafana/grafana/pkg/services/search/sort"
+ "github.com/grafana/grafana/pkg/services/searchV2"
+ "github.com/grafana/grafana/pkg/services/searchusers"
+ "github.com/grafana/grafana/pkg/services/searchusers/filters"
+ "github.com/grafana/grafana/pkg/services/secrets"
+ "github.com/grafana/grafana/pkg/services/secrets/database"
+ kvstore2 "github.com/grafana/grafana/pkg/services/secrets/kvstore"
+ migrations2 "github.com/grafana/grafana/pkg/services/secrets/kvstore/migrations"
+ "github.com/grafana/grafana/pkg/services/secrets/manager"
+ "github.com/grafana/grafana/pkg/services/secrets/migrator"
+ "github.com/grafana/grafana/pkg/services/serviceaccounts"
+ "github.com/grafana/grafana/pkg/services/serviceaccounts/extsvcaccounts"
+ manager2 "github.com/grafana/grafana/pkg/services/serviceaccounts/manager"
+ "github.com/grafana/grafana/pkg/services/serviceaccounts/proxy"
+ "github.com/grafana/grafana/pkg/services/serviceaccounts/retriever"
+ "github.com/grafana/grafana/pkg/services/shorturls"
+ "github.com/grafana/grafana/pkg/services/shorturls/shorturlimpl"
+ "github.com/grafana/grafana/pkg/services/signingkeys"
+ "github.com/grafana/grafana/pkg/services/signingkeys/signingkeysimpl"
+ "github.com/grafana/grafana/pkg/services/sqlstore"
+ "github.com/grafana/grafana/pkg/services/sqlstore/migrations"
+ "github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
+ "github.com/grafana/grafana/pkg/services/ssosettings"
+ "github.com/grafana/grafana/pkg/services/ssosettings/ssosettingsimpl"
+ api3 "github.com/grafana/grafana/pkg/services/star/api"
+ "github.com/grafana/grafana/pkg/services/star/starimpl"
+ "github.com/grafana/grafana/pkg/services/stats/statsimpl"
+ "github.com/grafana/grafana/pkg/services/store"
+ "github.com/grafana/grafana/pkg/services/store/resolver"
+ "github.com/grafana/grafana/pkg/services/store/sanitizer"
+ "github.com/grafana/grafana/pkg/services/supportbundles"
+ "github.com/grafana/grafana/pkg/services/supportbundles/bundleregistry"
+ "github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl"
+ "github.com/grafana/grafana/pkg/services/tag"
+ "github.com/grafana/grafana/pkg/services/tag/tagimpl"
+ "github.com/grafana/grafana/pkg/services/team/teamapi"
+ "github.com/grafana/grafana/pkg/services/team/teamimpl"
+ "github.com/grafana/grafana/pkg/services/temp_user"
+ "github.com/grafana/grafana/pkg/services/temp_user/tempuserimpl"
+ "github.com/grafana/grafana/pkg/services/updatemanager"
+ "github.com/grafana/grafana/pkg/services/user"
+ "github.com/grafana/grafana/pkg/services/user/userimpl"
+ "github.com/grafana/grafana/pkg/services/validations"
+ "github.com/grafana/grafana/pkg/setting"
+ "github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
+ database5 "github.com/grafana/grafana/pkg/storage/secret/database"
+ encryption2 "github.com/grafana/grafana/pkg/storage/secret/encryption"
+ "github.com/grafana/grafana/pkg/storage/secret/metadata"
+ migrator2 "github.com/grafana/grafana/pkg/storage/secret/migrator"
+ "github.com/grafana/grafana/pkg/storage/unified"
+ "github.com/grafana/grafana/pkg/storage/unified/resource"
+ "github.com/grafana/grafana/pkg/storage/unified/search"
+ "github.com/grafana/grafana/pkg/tsdb/azuremonitor"
+ "github.com/grafana/grafana/pkg/tsdb/cloud-monitoring"
+ "github.com/grafana/grafana/pkg/tsdb/cloudwatch"
+ "github.com/grafana/grafana/pkg/tsdb/elasticsearch"
+ "github.com/grafana/grafana/pkg/tsdb/grafana-postgresql-datasource"
+ "github.com/grafana/grafana/pkg/tsdb/grafana-pyroscope-datasource"
+ "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource"
+ "github.com/grafana/grafana/pkg/tsdb/grafanads"
+ "github.com/grafana/grafana/pkg/tsdb/graphite"
+ "github.com/grafana/grafana/pkg/tsdb/influxdb"
+ "github.com/grafana/grafana/pkg/tsdb/jaeger"
+ "github.com/grafana/grafana/pkg/tsdb/loki"
+ "github.com/grafana/grafana/pkg/tsdb/mssql"
+ "github.com/grafana/grafana/pkg/tsdb/mysql"
+ "github.com/grafana/grafana/pkg/tsdb/opentsdb"
+ "github.com/grafana/grafana/pkg/tsdb/parca"
+ "github.com/grafana/grafana/pkg/tsdb/prometheus"
+ "github.com/grafana/grafana/pkg/tsdb/tempo"
+ "github.com/grafana/grafana/pkg/tsdb/zipkin"
+ "github.com/stretchr/testify/mock"
+ "go.opentelemetry.io/otel"
+ "go.opentelemetry.io/otel/trace"
+)
+
+import (
+ _ "github.com/grafana/grafana/pkg/extensions"
+)
+
+// Injectors from wire.go:
+
+func Initialize(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*Server, error) {
+ routeRegisterImpl := routing.ProvideRegister()
+ tracingConfig, err := tracing.ProvideTracingConfig(cfg)
+ if err != nil {
+ return nil, err
+ }
+ tracingService, err := tracing.ProvideService(tracingConfig)
+ if err != nil {
+ return nil, err
+ }
+ inProcBus := bus.ProvideBus(tracingService)
+ featureManager, err := featuremgmt.ProvideManagerService(cfg)
+ if err != nil {
+ return nil, err
+ }
+ featureToggles := featuremgmt.ProvideToggles(featureManager)
+ ossMigrations := migrations.ProvideOSSMigrations(featureToggles)
+ sqlStore, err := sqlstore.ProvideService(cfg, featureToggles, ossMigrations, inProcBus, tracingService)
+ if err != nil {
+ return nil, err
+ }
+ kvStore := kvstore.ProvideService(sqlStore)
+ accessControl := acimpl.ProvideAccessControl(featureToggles)
+ bundleregistryService := bundleregistry.ProvideService()
+ usageStats, err := service.ProvideService(cfg, kvStore, routeRegisterImpl, tracingService, accessControl, bundleregistryService)
+ if err != nil {
+ return nil, err
+ }
+ secretsStoreImpl := database.ProvideSecretsStore(sqlStore)
+ providerProvider := provider.ProvideEncryptionProvider()
+ serviceService, err := service2.ProvideEncryptionService(tracingService, providerProvider, usageStats, cfg)
+ if err != nil {
+ return nil, err
+ }
+ osskmsprovidersService := osskmsproviders.ProvideService(serviceService, cfg, featureToggles)
+ secretsService, err := manager.ProvideSecretsService(tracingService, secretsStoreImpl, osskmsprovidersService, serviceService, cfg, featureToggles, usageStats)
+ if err != nil {
+ return nil, err
+ }
+ remoteCache, err := remotecache.ProvideService(cfg, sqlStore, usageStats, secretsService)
+ if err != nil {
+ return nil, err
+ }
+ ossImpl := setting.ProvideProvider(cfg)
+ pluginManagementCfg, err := pluginconfig.ProvidePluginManagementConfig(cfg, ossImpl, featureToggles)
+ if err != nil {
+ return nil, err
+ }
+ pluginInstanceCfg, err := pluginconfig.ProvidePluginInstanceConfig(cfg, ossImpl, featureToggles)
+ if err != nil {
+ return nil, err
+ }
+ hooksService := hooks.ProvideService()
+ ossLicensingService := licensing.ProvideService(cfg, hooksService)
+ licensingService := licensing2.ProvideLicensing(cfg, ossLicensingService)
+ envVarsProvider := pluginconfig.NewEnvVarsProvider(pluginInstanceCfg, licensingService)
+ inMemory := registry.ProvideService()
+ rendererManager, err := renderer.ProvideService(pluginManagementCfg, envVarsProvider, inMemory, tracingService)
+ if err != nil {
+ return nil, err
+ }
+ renderingService, err := rendering.ProvideService(cfg, featureToggles, remoteCache, rendererManager)
+ if err != nil {
+ return nil, err
+ }
+ cacheService := localcache.ProvideService()
+ ossDataSourceRequestValidator := validations.ProvideValidator()
+ sourcesService := sources.ProvideService(cfg, pluginManagementCfg)
+ discovery := pipeline.ProvideDiscoveryStage(pluginManagementCfg, inMemory)
+ keystoreService := keystore.ProvideService(kvStore)
+ keyRetriever := dynamic.ProvideService(cfg, keystoreService)
+ keyretrieverService := keyretriever.ProvideService(keyRetriever)
+ signatureSignature := signature.ProvideService(pluginManagementCfg, keyretrieverService)
+ pluginscdnService := pluginscdn.ProvideService(pluginManagementCfg)
+ assetpathService := assetpath.ProvideService(pluginManagementCfg, pluginscdnService)
+ bootstrap := pipeline.ProvideBootstrapStage(pluginManagementCfg, signatureSignature, assetpathService)
+ unsignedPluginAuthorizer := signature.ProvideOSSAuthorizer(pluginManagementCfg)
+ validation := signature.ProvideValidatorService(unsignedPluginAuthorizer)
+ angularpatternsstoreService := angularpatternsstore.ProvideService(kvStore)
+ angulardetectorsproviderDynamic, err := angulardetectorsprovider.ProvideDynamic(cfg, angularpatternsstoreService)
+ if err != nil {
+ return nil, err
+ }
+ angularinspectorService, err := angularinspector.ProvideService(angulardetectorsproviderDynamic)
+ if err != nil {
+ return nil, err
+ }
+ validate := pipeline.ProvideValidationStage(pluginManagementCfg, validation, angularinspectorService)
+ ossDataSourceRequestURLValidator := validations.ProvideURLValidator()
+ httpclientProvider := httpclientprovider.New(cfg, ossDataSourceRequestURLValidator, tracingService)
+ azuremonitorService := azuremonitor.ProvideService(httpclientProvider)
+ cloudwatchService := cloudwatch.ProvideService()
+ cloudmonitoringService := cloudmonitoring.ProvideService(httpclientProvider)
+ elasticsearchService := elasticsearch.ProvideService(httpclientProvider)
+ graphiteService := graphite.ProvideService(httpclientProvider, tracingService)
+ influxdbService := influxdb.ProvideService(httpclientProvider, featureToggles)
+ tracer := otelTracer()
+ lokiService := loki.ProvideService(httpclientProvider, tracer)
+ opentsdbService := opentsdb.ProvideService(httpclientProvider)
+ prometheusService := prometheus.ProvideService(httpclientProvider)
+ tempoService := tempo.ProvideService(httpclientProvider)
+ testdatasourceService := testdatasource.ProvideService()
+ postgresService := postgres.ProvideService(cfg)
+ mysqlService := mysql.ProvideService()
+ mssqlService := mssql.ProvideService(cfg)
+ entityEventsService := store.ProvideEntityEventsService(cfg, sqlStore, featureToggles)
+ quotaService := quotaimpl.ProvideService(sqlStore, cfg)
+ orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService)
+ if err != nil {
+ return nil, err
+ }
+ teamService, err := teamimpl.ProvideService(sqlStore, cfg, tracingService)
+ if err != nil {
+ return nil, err
+ }
+ userService, err := userimpl.ProvideService(sqlStore, orgService, cfg, teamService, cacheService, tracingService, quotaService, bundleregistryService)
+ if err != nil {
+ return nil, err
+ }
+ actionSetService := resourcepermissions.NewActionSetService()
+ permissionRegistry := permreg.ProvidePermissionRegistry()
+ serverLockService := serverlock.ProvideService(sqlStore, tracingService)
+ acimplService, err := acimpl.ProvideService(cfg, sqlStore, routeRegisterImpl, cacheService, accessControl, userService, actionSetService, featureToggles, tracingService, permissionRegistry, serverLockService)
+ if err != nil {
+ return nil, err
+ }
+ folderStoreImpl := folderimpl.ProvideStore(sqlStore)
+ tagimplService := tagimpl.ProvideService(sqlStore)
+ dashboardsStore, err := database2.ProvideDashboardStore(sqlStore, cfg, featureToggles, tagimplService)
+ if err != nil {
+ return nil, err
+ }
+ dashboardFolderStoreImpl := folderimpl.ProvideDashboardFolderStore(sqlStore)
+ publicDashboardStoreImpl := database3.ProvideStore(sqlStore, cfg, featureToggles)
+ publicDashboardServiceWrapperImpl := service3.ProvideServiceWrapper(publicDashboardStoreImpl)
+ registerer := metrics.ProvideRegisterer()
+ apikeyService, err := apikeyimpl.ProvideService(sqlStore, cfg, quotaService)
+ if err != nil {
+ return nil, err
+ }
+ contextHandler := grpccontext.ProvideContextHandler(tracingService)
+ authenticator := interceptors.ProvideAuthenticator(apikeyService, userService, acimplService, contextHandler)
+ grpcserverProvider, err := grpcserver.ProvideService(cfg, featureToggles, authenticator, tracer, registerer)
+ if err != nil {
+ return nil, err
+ }
+ client, err := authz.ProvideZanzana(cfg, sqlStore, tracingService, featureToggles, registerer)
+ if err != nil {
+ return nil, err
+ }
+ eventualRestConfigProvider := apiserver.ProvideEventualRestConfigProvider()
+ accessClient, err := authz.ProvideAuthZClient(cfg, featureToggles, grpcserverProvider, tracingService, registerer, sqlStore, acimplService, client, eventualRestConfigProvider)
+ if err != nil {
+ return nil, err
+ }
+ ossDashboardStats := search.ProvideDashboardStats()
+ documentBuilderSupplier := search.ProvideDocumentBuilders(sqlStore, ossDashboardStats)
+ options := &unified.Options{
+ Cfg: cfg,
+ Features: featureToggles,
+ DB: sqlStore,
+ Tracer: tracingService,
+ Reg: registerer,
+ Authzc: accessClient,
+ Docs: documentBuilderSupplier,
+ }
+ storageMetrics := resource.ProvideStorageMetrics(registerer)
+ bleveIndexMetrics := resource.ProvideIndexMetrics(registerer)
+ resourceClient, err := unified.ProvideUnifiedStorageClient(options, storageMetrics, bleveIndexMetrics)
+ if err != nil {
+ return nil, err
+ }
+ dualwriteService := dualwrite.ProvideService(featureToggles, registerer, kvStore, cfg)
+ sortService := sort.ProvideService()
+ folderimplService := folderimpl.ProvideService(folderStoreImpl, accessControl, inProcBus, dashboardsStore, dashboardFolderStoreImpl, userService, sqlStore, featureToggles, bundleregistryService, publicDashboardServiceWrapperImpl, cfg, registerer, tracer, resourceClient, dualwriteService, sortService, eventualRestConfigProvider)
+ searchService := searchV2.ProvideService(cfg, sqlStore, entityEventsService, acimplService, tracingService, featureToggles, orgService, userService, folderimplService)
+ systemUsers := store.ProvideSystemUsersService()
+ storageService, err := store.ProvideService(sqlStore, featureToggles, cfg, quotaService, systemUsers)
+ if err != nil {
+ return nil, err
+ }
+ grafanadsService := grafanads.ProvideService(searchService, storageService, featureToggles)
+ pyroscopeService := pyroscope.ProvideService(httpclientProvider)
+ parcaService := parca.ProvideService(httpclientProvider)
+ zipkinService := zipkin.ProvideService(httpclientProvider)
+ jaegerService := jaeger.ProvideService(httpclientProvider)
+ corepluginRegistry := coreplugin.ProvideCoreRegistry(tracingService, azuremonitorService, cloudwatchService, cloudmonitoringService, elasticsearchService, graphiteService, influxdbService, lokiService, opentsdbService, prometheusService, tempoService, testdatasourceService, postgresService, mysqlService, mssqlService, grafanadsService, pyroscopeService, parcaService, zipkinService, jaegerService)
+ providerService := provider2.ProvideService(corepluginRegistry)
+ processService := process.ProvideService()
+ retrieverService := retriever.ProvideService(sqlStore, apikeyService, kvStore, userService, orgService)
+ serviceAccountPermissionsService, err := ossaccesscontrol.ProvideServiceAccountPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, retrieverService, acimplService, teamService, userService, actionSetService)
+ if err != nil {
+ return nil, err
+ }
+ serviceAccountsService, err := manager2.ProvideServiceAccountsService(cfg, usageStats, sqlStore, apikeyService, kvStore, userService, orgService, acimplService, serviceAccountPermissionsService, serverLockService)
+ if err != nil {
+ return nil, err
+ }
+ extSvcAccountsService := extsvcaccounts.ProvideExtSvcAccountsService(acimplService, cfg, inProcBus, sqlStore, featureToggles, registerer, serviceAccountsService, secretsService, tracingService)
+ registryRegistry := registry2.ProvideExtSvcRegistry(cfg, extSvcAccountsService, serverLockService, featureToggles)
+ service11 := service4.ProvideService(sqlStore, secretsService)
+ serviceregistrationService := serviceregistration.ProvideService(cfg, featureToggles, registryRegistry, service11)
+ initialize := pipeline.ProvideInitializationStage(pluginManagementCfg, inMemory, providerService, processService, serviceregistrationService, acimplService, actionSetService, envVarsProvider, tracingService)
+ terminate, err := pipeline.ProvideTerminationStage(pluginManagementCfg, inMemory, processService)
+ if err != nil {
+ return nil, err
+ }
+ errorRegistry := pluginerrs.ProvideErrorTracker()
+ loaderLoader := loader.ProvideService(pluginManagementCfg, discovery, bootstrap, validate, initialize, terminate, errorRegistry)
+ pluginstoreService, err := pluginstore.ProvideService(inMemory, sourcesService, loaderLoader)
+ if err != nil {
+ return nil, err
+ }
+ filestoreService := filestore.ProvideService(inMemory)
+ fileStoreManager := dashboards.ProvideFileStoreManager(pluginstoreService, filestoreService)
+ folderPermissionsService, err := ossaccesscontrol.ProvideFolderPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, folderimplService, acimplService, teamService, userService, actionSetService)
+ if err != nil {
+ return nil, err
+ }
+ dashboardServiceImpl, err := service5.ProvideDashboardServiceImpl(cfg, dashboardsStore, dashboardFolderStoreImpl, featureToggles, folderPermissionsService, accessControl, acimplService, folderimplService, registerer, eventualRestConfigProvider, userService, quotaService, orgService, publicDashboardServiceWrapperImpl, resourceClient, dualwriteService, sortService, serverLockService, kvStore)
+ if err != nil {
+ return nil, err
+ }
+ pluginService := service5.ProvideDashboardPluginService(featureToggles, dashboardServiceImpl)
+ service12 := service6.ProvideService(fileStoreManager, pluginService)
+ orgRoleMapper := connectors.ProvideOrgRoleMapper(cfg, orgService)
+ ssosettingsimplService := ssosettingsimpl.ProvideService(cfg, sqlStore, accessControl, routeRegisterImpl, featureToggles, secretsService, usageStats, registerer, ossImpl, ossLicensingService)
+ socialService := socialimpl.ProvideService(cfg, featureToggles, usageStats, bundleregistryService, remoteCache, orgRoleMapper, ssosettingsimplService)
+ loginStore := authinfoimpl.ProvideStore(sqlStore, secretsService)
+ authinfoimplService := authinfoimpl.ProvideService(loginStore, remoteCache, secretsService)
+ userAuthTokenService, err := authimpl.ProvideUserAuthTokenService(sqlStore, serverLockService, quotaService, secretsService, cfg, tracingService, featureToggles)
+ if err != nil {
+ return nil, err
+ }
+ oauthtokenService := oauthtoken.ProvideService(socialService, authinfoimplService, cfg, registerer, serverLockService, tracingService, userAuthTokenService, featureToggles)
+ ossCachingService := caching.ProvideCachingService()
+ middlewareHandler, err := pluginsintegration.ProvideClientWithMiddlewares(cfg, inMemory, oauthtokenService, tracingService, ossCachingService, featureToggles, registerer)
+ if err != nil {
+ return nil, err
+ }
+ pluginerrsStore := pluginerrs.ProvideStore(errorRegistry)
+ repoManager, err := repo.ProvideService(pluginManagementCfg)
+ if err != nil {
+ return nil, err
+ }
+ pluginInstaller := manager3.ProvideInstaller(pluginManagementCfg, inMemory, loaderLoader, repoManager, serviceregistrationService)
+ ossProvider := guardian.ProvideGuardian()
+ cacheServiceImpl := service7.ProvideCacheService(cacheService, sqlStore, ossProvider)
+ shortURLService := shorturlimpl.ProvideService(sqlStore)
+ queryHistoryService := queryhistory.ProvideService(cfg, sqlStore, routeRegisterImpl, accessControl)
+ dashboardService := service5.ProvideDashboardService(featureToggles, dashboardServiceImpl)
+ dashverService := dashverimpl.ProvideService(cfg, sqlStore, dashboardService, dashboardsStore, featureToggles, eventualRestConfigProvider, userService, resourceClient, dualwriteService, sortService)
+ dashboardSnapshotStore := database4.ProvideStore(sqlStore, cfg)
+ serviceImpl := service8.ProvideService(dashboardSnapshotStore, secretsService, dashboardService)
+ dBstore, err := store2.ProvideDBStore(cfg, featureToggles, sqlStore, folderimplService, dashboardService, accessControl, inProcBus)
+ if err != nil {
+ return nil, err
+ }
+ deleteExpiredService := image.ProvideDeleteExpiredService(dBstore)
+ tempuserService := tempuserimpl.ProvideService(sqlStore, cfg)
+ cleanupServiceImpl := annotationsimpl.ProvideCleanupService(sqlStore, cfg)
+ cleanUpService := cleanup.ProvideService(cfg, serverLockService, shortURLService, sqlStore, queryHistoryService, dashverService, serviceImpl, deleteExpiredService, tempuserService, tracingService, cleanupServiceImpl, dashboardService, dBstore)
+ secretsKVStore, err := kvstore2.ProvideService(sqlStore, secretsService)
+ if err != nil {
+ return nil, err
+ }
+ datasourcePermissionsService := ossaccesscontrol.ProvideDatasourcePermissionsService(cfg, featureToggles, sqlStore)
+ requestConfigProvider := pluginconfig.NewRequestConfigProvider(pluginInstanceCfg)
+ baseProvider := plugincontext.ProvideBaseService(cfg, requestConfigProvider)
+ service13, err := service7.ProvideService(sqlStore, secretsService, secretsKVStore, cfg, featureToggles, accessControl, datasourcePermissionsService, quotaService, pluginstoreService, middlewareHandler, baseProvider)
+ if err != nil {
+ return nil, err
+ }
+ correlationsService, err := correlations.ProvideService(sqlStore, routeRegisterImpl, service13, accessControl, inProcBus, quotaService, cfg)
+ if err != nil {
+ return nil, err
+ }
+ mailer, err := notifications.ProvideSmtpService(cfg)
+ if err != nil {
+ return nil, err
+ }
+ notificationService, err := notifications.ProvideService(inProcBus, cfg, mailer, tempuserService)
+ if err != nil {
+ return nil, err
+ }
+ dashboardProvisioningService := service5.ProvideDashboardProvisioningService(featureToggles, dashboardServiceImpl)
+ receiverPermissionsService, err := ossaccesscontrol.ProvideReceiverPermissionsService(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService)
+ if err != nil {
+ return nil, err
+ }
+ provisioningServiceImpl, err := provisioning.ProvideService(accessControl, cfg, sqlStore, pluginstoreService, dBstore, serviceService, notificationService, dashboardProvisioningService, service13, correlationsService, dashboardService, folderimplService, service11, searchService, quotaService, secretsService, orgService, receiverPermissionsService, tracingService, dualwriteService)
+ if err != nil {
+ return nil, err
+ }
+ dataSourceProxyService := datasourceproxy.ProvideService(cacheServiceImpl, ossDataSourceRequestValidator, pluginstoreService, cfg, httpclientProvider, oauthtokenService, service13, tracingService, secretsService, featureToggles)
+ starService := starimpl.ProvideService(sqlStore)
+ searchSearchService := search2.ProvideService(cfg, sqlStore, starService, dashboardService, folderimplService, featureToggles, sortService)
+ plugincontextProvider := plugincontext.ProvideService(cfg, cacheService, pluginstoreService, cacheServiceImpl, service13, service11, requestConfigProvider)
+ exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService)
+ queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider)
+ repositoryImpl := annotationsimpl.ProvideService(sqlStore, cfg, featureToggles, tagimplService, tracingService, dBstore, dashboardService, registerer)
+ grafanaLive, err := live.ProvideService(plugincontextProvider, cfg, routeRegisterImpl, pluginstoreService, middlewareHandler, cacheService, cacheServiceImpl, sqlStore, secretsService, usageStats, queryServiceImpl, featureToggles, accessControl, dashboardService, repositoryImpl, orgService, eventualRestConfigProvider)
+ if err != nil {
+ return nil, err
+ }
+ gateway := pushhttp.ProvideService(cfg, grafanaLive)
+ authnimplService := authnimpl.ProvideService(cfg, tracingService, userAuthTokenService, usageStats, registerer, authinfoimplService)
+ authnAuthenticator := authnimpl.ProvideAuthnServiceAuthenticateOnly(authnimplService)
+ contexthandlerContextHandler := contexthandler.ProvideService(cfg, authnAuthenticator, featureToggles)
+ logger := loggermw.Provide(cfg, featureToggles)
+ ngAlert := metrics2.ProvideService()
+ alertNG, err := ngalert.ProvideService(cfg, featureToggles, cacheServiceImpl, service13, routeRegisterImpl, sqlStore, kvStore, exprService, dataSourceProxyService, quotaService, secretsService, notificationService, ngAlert, folderimplService, accessControl, dashboardService, renderingService, inProcBus, acimplService, repositoryImpl, pluginstoreService, tracingService, dBstore, httpclientProvider, plugincontextProvider, receiverPermissionsService, userService)
+ if err != nil {
+ return nil, err
+ }
+ libraryElementService := libraryelements.ProvideService(cfg, sqlStore, routeRegisterImpl, folderimplService, featureToggles, accessControl, dashboardService, eventualRestConfigProvider, userService)
+ libraryPanelService, err := librarypanels.ProvideService(cfg, sqlStore, routeRegisterImpl, libraryElementService, folderimplService)
+ if err != nil {
+ return nil, err
+ }
+ grafanaService, err := updatemanager.ProvideGrafanaService(cfg, tracingService)
+ if err != nil {
+ return nil, err
+ }
+ noop := managedplugins.NewNoop()
+ provisionedpluginsNoop := provisionedplugins.NewNoop()
+ preinstallImpl := pluginchecker.ProvidePreinstall(cfg)
+ plugincheckerService := pluginchecker.ProvideService(noop, provisionedpluginsNoop, preinstallImpl)
+ pluginsService, err := updatemanager.ProvidePluginsService(cfg, pluginstoreService, pluginInstaller, tracingService, featureToggles, plugincheckerService)
+ if err != nil {
+ return nil, err
+ }
+ ossSearchUserFilter := filters.ProvideOSSSearchUserFilter()
+ ossService := searchusers.ProvideUsersService(cfg, ossSearchUserFilter, userService)
+ serviceAccountsProxy, err := proxy.ProvideServiceAccountsProxy(cfg, accessControl, acimplService, featureToggles, serviceAccountPermissionsService, serviceAccountsService, routeRegisterImpl)
+ if err != nil {
+ return nil, err
+ }
+ pluginassetsService := pluginassets.ProvideService(pluginManagementCfg, pluginscdnService, signatureSignature, pluginstoreService)
+ avatarCacheServer := avatar.ProvideAvatarCacheServer(cfg)
+ prefService := prefimpl.ProvideService(sqlStore, cfg)
+ dashboardPermissionsService, err := ossaccesscontrol.ProvideDashboardPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, dashboardService, folderimplService, acimplService, teamService, userService, actionSetService, dashboardServiceImpl)
+ if err != nil {
+ return nil, err
+ }
+ csrfCSRF := csrf.ProvideCSRFFilter(cfg)
+ playlistService := playlistimpl.ProvideService(sqlStore, tracingService)
+ secretsMigrator := migrator.ProvideSecretsMigrator(serviceService, secretsService, sqlStore, ossImpl, featureToggles)
+ dataSourceSecretMigrationService := migrations2.ProvideDataSourceMigrationService(service13, kvStore, featureToggles)
+ secretMigrationProviderImpl := migrations2.ProvideSecretMigrationProvider(serverLockService, dataSourceSecretMigrationService)
+ publicDashboardServiceImpl := service3.ProvideService(cfg, featureToggles, publicDashboardStoreImpl, queryServiceImpl, repositoryImpl, accessControl, publicDashboardServiceWrapperImpl, dashboardService, ossLicensingService)
+ middleware := api2.ProvideMiddleware()
+ apiApi := api2.ProvideApi(publicDashboardServiceImpl, routeRegisterImpl, accessControl, featureToggles, middleware, cfg, ossLicensingService)
+ loginattemptimplService := loginattemptimpl.ProvideService(sqlStore, cfg, serverLockService)
+ deletionService, err := orgimpl.ProvideDeletionService(sqlStore, cfg, dashboardService, accessControl)
+ if err != nil {
+ return nil, err
+ }
+ authnService := authnimpl.ProvideAuthnService(authnimplService)
+ openFeatureService, err := featuremgmt.ProvideOpenFeatureService(cfg)
+ if err != nil {
+ return nil, err
+ }
+ navtreeService := navtreeimpl.ProvideService(cfg, accessControl, pluginstoreService, service11, starService, featureToggles, dashboardService, acimplService, kvStore, apikeyService, ossLicensingService, authnService, openFeatureService)
+ searchHTTPService := searchV2.ProvideSearchHTTPService(searchService)
+ statsService := statsimpl.ProvideService(cfg, sqlStore, dashboardService, folderimplService, orgService, featureToggles)
+ gatherer := metrics.ProvideGatherer()
+ apiAPI := api3.ProvideApi(starService, dashboardService)
+ anonUserLimitValidatorImpl := validator.ProvideAnonUserLimitValidator()
+ anonDeviceService := anonimpl.ProvideAnonymousDeviceService(usageStats, authnService, sqlStore, cfg, orgService, serverLockService, accessControl, routeRegisterImpl, anonUserLimitValidatorImpl)
+ signingkeysimplService, err := signingkeysimpl.ProvideEmbeddedSigningKeysService(sqlStore, secretsService, remoteCache, routeRegisterImpl)
+ if err != nil {
+ return nil, err
+ }
+ localSigner, err := idimpl.ProvideLocalSigner(signingkeysimplService)
+ if err != nil {
+ return nil, err
+ }
+ idimplService := idimpl.ProvideService(cfg, localSigner, remoteCache, authnService, registerer)
+ verifier := userimpl.ProvideVerifier(cfg, userService, tempuserService, notificationService, idimplService)
+ httpServer, err := api.ProvideHTTPServer(apiOpts, cfg, routeRegisterImpl, inProcBus, renderingService, ossLicensingService, hooksService, cacheService, sqlStore, ossDataSourceRequestValidator, pluginstoreService, service12, pluginstoreService, middlewareHandler, pluginerrsStore, pluginInstaller, ossImpl, cacheServiceImpl, userAuthTokenService, cleanUpService, shortURLService, queryHistoryService, correlationsService, remoteCache, provisioningServiceImpl, accessControl, dataSourceProxyService, searchSearchService, grafanaLive, gateway, plugincontextProvider, contexthandlerContextHandler, logger, featureToggles, alertNG, libraryPanelService, libraryElementService, quotaService, socialService, tracingService, serviceService, grafanaService, pluginsService, ossService, service13, queryServiceImpl, filestoreService, serviceAccountsProxy, pluginassetsService, authinfoimplService, storageService, notificationService, dashboardService, dashboardProvisioningService, folderimplService, ossProvider, serviceImpl, service11, avatarCacheServer, prefService, folderPermissionsService, dashboardPermissionsService, dashverService, starService, csrfCSRF, noop, playlistService, apikeyService, kvStore, secretsMigrator, secretsService, secretMigrationProviderImpl, secretsKVStore, apiApi, userService, tempuserService, loginattemptimplService, orgService, deletionService, teamService, acimplService, navtreeService, repositoryImpl, tagimplService, searchHTTPService, oauthtokenService, statsService, authnService, pluginscdnService, gatherer, apiAPI, registerer, eventualRestConfigProvider, anonDeviceService, verifier, preinstallImpl)
+ if err != nil {
+ return nil, err
+ }
+ validatorService, err := validator2.ProvideService(pluginstoreService)
+ if err != nil {
+ return nil, err
+ }
+ sandboxService := sandbox.ProvideService(cfg)
+ advisorService, err := advisor.ProvideService(cfg, eventualRestConfigProvider)
+ if err != nil {
+ return nil, err
+ }
+ statscollectorService := statscollector.ProvideService(usageStats, validatorService, statsService, cfg, sqlStore, socialService, pluginstoreService, featureManager, service13, httpclientProvider, sandboxService, advisorService)
+ internalMetricsService, err := metrics.ProvideService(cfg, registerer, gatherer)
+ if err != nil {
+ return nil, err
+ }
+ supportbundlesimplService, err := supportbundlesimpl.ProvideService(accessControl, acimplService, bundleregistryService, cfg, featureToggles, httpServer, kvStore, service11, pluginstoreService, routeRegisterImpl, ossImpl, sqlStore, usageStats, tracingService)
+ if err != nil {
+ return nil, err
+ }
+ metricService, err := metric.ProvideService(publicDashboardStoreImpl, registerer)
+ if err != nil {
+ return nil, err
+ }
+ scopedPluginDatasourceProvider := datasource.ProvideDefaultPluginConfigs(service13, cacheServiceImpl, plugincontextProvider)
+ v := builder.ProvideDefaultBuildHandlerChainFuncFromBuilders()
+ aggregatorRunner := aggregatorrunner.ProvideNoopAggregatorConfigurator()
+ apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner)
+ if err != nil {
+ return nil, err
+ }
+ pluginexternalService, err := pluginexternal.ProvideService(cfg, pluginstoreService)
+ if err != nil {
+ return nil, err
+ }
+ plugininstallerService, err := plugininstaller.ProvideService(cfg, pluginstoreService, pluginInstaller, registerer, repoManager, featureToggles, plugincheckerService)
+ if err != nil {
+ return nil, err
+ }
+ zanzanaReconciler := dualwrite2.ProvideZanzanaReconciler(cfg, featureToggles, client, sqlStore, serverLockService, folderimplService)
+ playlistAppProvider := playlist.RegisterApp(playlistService, cfg, featureToggles)
+ investigationsAppProvider := investigations.RegisterApp(cfg)
+ checkregistryService := checkregistry.ProvideService(service13, pluginstoreService, plugincontextProvider, middlewareHandler, plugincheckerService, repoManager, preinstallImpl, noop, provisionedpluginsNoop, ssosettingsimplService, cfg, pluginerrsStore)
+ advisorAppProvider := advisor2.RegisterApp(checkregistryService, cfg)
+ alertingNotificationsAppProvider := notifications2.RegisterApp(cfg, alertNG)
+ appregistryService, err := appregistry.ProvideRegistryServiceSink(apiserverService, eventualRestConfigProvider, featureToggles, playlistAppProvider, investigationsAppProvider, advisorAppProvider, alertingNotificationsAppProvider, cfg)
+ if err != nil {
+ return nil, err
+ }
+ importDashboardService := service9.ProvideService(routeRegisterImpl, quotaService, service12, pluginstoreService, libraryPanelService, dashboardService, accessControl, folderimplService, featureToggles)
+ dashboardUpdater := service6.ProvideDashboardUpdater(inProcBus, pluginstoreService, service12, importDashboardService, service11, pluginService, dashboardService)
+ sanitizerProvider := sanitizer.ProvideService(renderingService)
+ healthService, err := grpcserver.ProvideHealthService(cfg, grpcserverProvider)
+ if err != nil {
+ return nil, err
+ }
+ reflectionService, err := grpcserver.ProvideReflectionService(cfg, grpcserverProvider)
+ if err != nil {
+ return nil, err
+ }
+ ossGroups := ldap.ProvideGroupsService()
+ identitySynchronizer := authnimpl.ProvideIdentitySynchronizer(authnimplService)
+ ldapImpl := service10.ProvideService(cfg, featureToggles, ssosettingsimplService)
+ apiService := api4.ProvideService(cfg, routeRegisterImpl, accessControl, userService, authinfoimplService, ossGroups, identitySynchronizer, orgService, ldapImpl, userAuthTokenService, bundleregistryService)
+ dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service13, dashboardServiceImpl, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, dashboardFolderStoreImpl, libraryPanelService, eventualRestConfigProvider, userService)
+ snapshotsAPIBuilder := dashboardsnapshot.RegisterAPIService(serviceImpl, apiserverService, cfg, featureToggles, sqlStore, registerer)
+ featureFlagAPIBuilder := featuretoggle.RegisterAPIService(featureManager, accessControl, apiserverService, cfg, registerer)
+ dataSourceAPIBuilder, err := datasource.RegisterAPIService(featureToggles, apiserverService, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, accessControl, registerer)
+ if err != nil {
+ return nil, err
+ }
+ folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, registerer, resourceClient)
+ storageBackendImpl := noopstorage.ProvideStorageBackend()
+ identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, registerer, storageBackendImpl)
+ if err != nil {
+ return nil, err
+ }
+ legacyDataSourceLookup := service7.ProvideLegacyDataSourceLookup(service13)
+ queryAPIBuilder, err := query2.RegisterAPIService(featureToggles, apiserverService, service13, pluginstoreService, accessControl, middlewareHandler, plugincontextProvider, registerer, tracingService, legacyDataSourceLookup)
+ if err != nil {
+ return nil, err
+ }
+ userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer)
+ databaseDatabase := database5.ProvideDatabase(sqlStore)
+ secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(databaseDatabase, featureToggles)
+ if err != nil {
+ return nil, err
+ }
+ keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(databaseDatabase, featureToggles)
+ if err != nil {
+ return nil, err
+ }
+ secretDBMigrator := migrator2.NewWithEngine(sqlStore)
+ secretAPIBuilder, err := secret.RegisterAPIService(featureToggles, cfg, apiserverService, tracingService, secureValueMetadataStorage, keeperMetadataStorage, accessClient, acimplService, secretDBMigrator)
+ if err != nil {
+ return nil, err
+ }
+ factory := github.ProvideFactory()
+ legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, accessControl)
+ webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, secretsService, factory, renderingService, resourceClient, eventualRestConfigProvider)
+ v2 := apiregistry.MergeProvisioningExtras(webhookExtraBuilder)
+ apiBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, factory, accessClient, legacyMigrator, dualwriteService, usageStats, secretsService, v2)
+ if err != nil {
+ return nil, err
+ }
+ staticFlagEvaluator, err := featuremgmt.ProvideStaticEvaluator(cfg)
+ if err != nil {
+ return nil, err
+ }
+ ofrepAPIBuilder := ofrep.RegisterAPIService(apiserverService, cfg, staticFlagEvaluator)
+ apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, secretAPIBuilder, apiBuilder, ofrepAPIBuilder)
+ teamPermissionsService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService)
+ if err != nil {
+ return nil, err
+ }
+ teamAPI := teamapi.ProvideTeamAPI(routeRegisterImpl, teamService, acimplService, accessControl, teamPermissionsService, userService, ossLicensingService, cfg, prefService, dashboardService, featureToggles)
+ cloudmigrationService, err := cloudmigrationimpl.ProvideService(cfg, httpclientProvider, featureToggles, sqlStore, service13, secretsKVStore, secretsService, routeRegisterImpl, registerer, tracingService, dashboardService, folderimplService, pluginstoreService, service11, accessControl, acimplService, kvStore, libraryElementService, alertNG)
+ if err != nil {
+ return nil, err
+ }
+ authService, err := jwt.ProvideService(cfg, remoteCache)
+ if err != nil {
+ return nil, err
+ }
+ ossUserProtectionImpl := authinfoimpl.ProvideOSSUserProtectionService()
+ registration := authnimpl.ProvideRegistration(cfg, authnService, orgService, userAuthTokenService, acimplService, permissionRegistry, apikeyService, userService, authService, ossUserProtectionImpl, loginattemptimplService, quotaService, authinfoimplService, renderingService, featureToggles, oauthtokenService, socialService, remoteCache, ldapImpl, ossImpl, tracingService, tempuserService, notificationService)
+ backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, dashboardServiceImpl, serviceImpl, serviceAccountsProxy, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration)
+ usageStatsProvidersRegistry := usagestatssvcs.ProvideUsageStatsProvidersRegistry(acimplService, userService)
+ server, err := New(opts, cfg, httpServer, acimplService, provisioningServiceImpl, backgroundServiceRegistry, usageStatsProvidersRegistry, statscollectorService, registerer)
+ if err != nil {
+ return nil, err
+ }
+ return server, nil
+}
+
+func InitializeForTest(t sqlutil.ITestDB, testingT interface {
+ Cleanup(func())
+ mock.TestingT
+}, cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*TestEnv, error) {
+ routeRegisterImpl := routing.ProvideRegister()
+ tracingConfig, err := tracing.ProvideTracingConfig(cfg)
+ if err != nil {
+ return nil, err
+ }
+ tracingService, err := tracing.ProvideService(tracingConfig)
+ if err != nil {
+ return nil, err
+ }
+ inProcBus := bus.ProvideBus(tracingService)
+ featureManager, err := featuremgmt.ProvideManagerService(cfg)
+ if err != nil {
+ return nil, err
+ }
+ featureToggles := featuremgmt.ProvideToggles(featureManager)
+ ossMigrations := migrations.ProvideOSSMigrations(featureToggles)
+ sqlStore, err := sqlstore.ProvideServiceForTests(t, cfg, featureToggles, inProcBus, ossMigrations)
+ if err != nil {
+ return nil, err
+ }
+ kvStore := kvstore.ProvideService(sqlStore)
+ accessControl := acimpl.ProvideAccessControl(featureToggles)
+ bundleregistryService := bundleregistry.ProvideService()
+ usageStats, err := service.ProvideService(cfg, kvStore, routeRegisterImpl, tracingService, accessControl, bundleregistryService)
+ if err != nil {
+ return nil, err
+ }
+ secretsStoreImpl := database.ProvideSecretsStore(sqlStore)
+ providerProvider := provider.ProvideEncryptionProvider()
+ serviceService, err := service2.ProvideEncryptionService(tracingService, providerProvider, usageStats, cfg)
+ if err != nil {
+ return nil, err
+ }
+ osskmsprovidersService := osskmsproviders.ProvideService(serviceService, cfg, featureToggles)
+ secretsService, err := manager.ProvideSecretsService(tracingService, secretsStoreImpl, osskmsprovidersService, serviceService, cfg, featureToggles, usageStats)
+ if err != nil {
+ return nil, err
+ }
+ remoteCache, err := remotecache.ProvideService(cfg, sqlStore, usageStats, secretsService)
+ if err != nil {
+ return nil, err
+ }
+ ossImpl := setting.ProvideProvider(cfg)
+ pluginManagementCfg, err := pluginconfig.ProvidePluginManagementConfig(cfg, ossImpl, featureToggles)
+ if err != nil {
+ return nil, err
+ }
+ pluginInstanceCfg, err := pluginconfig.ProvidePluginInstanceConfig(cfg, ossImpl, featureToggles)
+ if err != nil {
+ return nil, err
+ }
+ hooksService := hooks.ProvideService()
+ ossLicensingService := licensing.ProvideService(cfg, hooksService)
+ licensingService := licensing2.ProvideLicensing(cfg, ossLicensingService)
+ envVarsProvider := pluginconfig.NewEnvVarsProvider(pluginInstanceCfg, licensingService)
+ inMemory := registry.ProvideService()
+ rendererManager, err := renderer.ProvideService(pluginManagementCfg, envVarsProvider, inMemory, tracingService)
+ if err != nil {
+ return nil, err
+ }
+ renderingService, err := rendering.ProvideService(cfg, featureToggles, remoteCache, rendererManager)
+ if err != nil {
+ return nil, err
+ }
+ cacheService := localcache.ProvideService()
+ ossDataSourceRequestValidator := validations.ProvideValidator()
+ sourcesService := sources.ProvideService(cfg, pluginManagementCfg)
+ discovery := pipeline.ProvideDiscoveryStage(pluginManagementCfg, inMemory)
+ keystoreService := keystore.ProvideService(kvStore)
+ keyRetriever := dynamic.ProvideService(cfg, keystoreService)
+ keyretrieverService := keyretriever.ProvideService(keyRetriever)
+ signatureSignature := signature.ProvideService(pluginManagementCfg, keyretrieverService)
+ pluginscdnService := pluginscdn.ProvideService(pluginManagementCfg)
+ assetpathService := assetpath.ProvideService(pluginManagementCfg, pluginscdnService)
+ bootstrap := pipeline.ProvideBootstrapStage(pluginManagementCfg, signatureSignature, assetpathService)
+ unsignedPluginAuthorizer := signature.ProvideOSSAuthorizer(pluginManagementCfg)
+ validation := signature.ProvideValidatorService(unsignedPluginAuthorizer)
+ angularpatternsstoreService := angularpatternsstore.ProvideService(kvStore)
+ angulardetectorsproviderDynamic, err := angulardetectorsprovider.ProvideDynamic(cfg, angularpatternsstoreService)
+ if err != nil {
+ return nil, err
+ }
+ angularinspectorService, err := angularinspector.ProvideService(angulardetectorsproviderDynamic)
+ if err != nil {
+ return nil, err
+ }
+ validate := pipeline.ProvideValidationStage(pluginManagementCfg, validation, angularinspectorService)
+ ossDataSourceRequestURLValidator := validations.ProvideURLValidator()
+ httpclientProvider := httpclientprovider.New(cfg, ossDataSourceRequestURLValidator, tracingService)
+ azuremonitorService := azuremonitor.ProvideService(httpclientProvider)
+ cloudwatchService := cloudwatch.ProvideService()
+ cloudmonitoringService := cloudmonitoring.ProvideService(httpclientProvider)
+ elasticsearchService := elasticsearch.ProvideService(httpclientProvider)
+ graphiteService := graphite.ProvideService(httpclientProvider, tracingService)
+ influxdbService := influxdb.ProvideService(httpclientProvider, featureToggles)
+ tracer := otelTracer()
+ lokiService := loki.ProvideService(httpclientProvider, tracer)
+ opentsdbService := opentsdb.ProvideService(httpclientProvider)
+ prometheusService := prometheus.ProvideService(httpclientProvider)
+ tempoService := tempo.ProvideService(httpclientProvider)
+ testdatasourceService := testdatasource.ProvideService()
+ postgresService := postgres.ProvideService(cfg)
+ mysqlService := mysql.ProvideService()
+ mssqlService := mssql.ProvideService(cfg)
+ entityEventsService := store.ProvideEntityEventsService(cfg, sqlStore, featureToggles)
+ quotaService := quotaimpl.ProvideService(sqlStore, cfg)
+ orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService)
+ if err != nil {
+ return nil, err
+ }
+ teamService, err := teamimpl.ProvideService(sqlStore, cfg, tracingService)
+ if err != nil {
+ return nil, err
+ }
+ userService, err := userimpl.ProvideService(sqlStore, orgService, cfg, teamService, cacheService, tracingService, quotaService, bundleregistryService)
+ if err != nil {
+ return nil, err
+ }
+ actionSetService := resourcepermissions.NewActionSetService()
+ permissionRegistry := permreg.ProvidePermissionRegistry()
+ serverLockService := serverlock.ProvideService(sqlStore, tracingService)
+ acimplService, err := acimpl.ProvideService(cfg, sqlStore, routeRegisterImpl, cacheService, accessControl, userService, actionSetService, featureToggles, tracingService, permissionRegistry, serverLockService)
+ if err != nil {
+ return nil, err
+ }
+ folderStoreImpl := folderimpl.ProvideStore(sqlStore)
+ tagimplService := tagimpl.ProvideService(sqlStore)
+ dashboardsStore, err := database2.ProvideDashboardStore(sqlStore, cfg, featureToggles, tagimplService)
+ if err != nil {
+ return nil, err
+ }
+ dashboardFolderStoreImpl := folderimpl.ProvideDashboardFolderStore(sqlStore)
+ publicDashboardStoreImpl := database3.ProvideStore(sqlStore, cfg, featureToggles)
+ publicDashboardServiceWrapperImpl := service3.ProvideServiceWrapper(publicDashboardStoreImpl)
+ registerer := metrics.ProvideRegistererForTest()
+ apikeyService, err := apikeyimpl.ProvideService(sqlStore, cfg, quotaService)
+ if err != nil {
+ return nil, err
+ }
+ contextHandler := grpccontext.ProvideContextHandler(tracingService)
+ authenticator := interceptors.ProvideAuthenticator(apikeyService, userService, acimplService, contextHandler)
+ grpcserverProvider, err := grpcserver.ProvideService(cfg, featureToggles, authenticator, tracer, registerer)
+ if err != nil {
+ return nil, err
+ }
+ client, err := authz.ProvideZanzana(cfg, sqlStore, tracingService, featureToggles, registerer)
+ if err != nil {
+ return nil, err
+ }
+ eventualRestConfigProvider := apiserver.ProvideEventualRestConfigProvider()
+ accessClient, err := authz.ProvideAuthZClient(cfg, featureToggles, grpcserverProvider, tracingService, registerer, sqlStore, acimplService, client, eventualRestConfigProvider)
+ if err != nil {
+ return nil, err
+ }
+ ossDashboardStats := search.ProvideDashboardStats()
+ documentBuilderSupplier := search.ProvideDocumentBuilders(sqlStore, ossDashboardStats)
+ options := &unified.Options{
+ Cfg: cfg,
+ Features: featureToggles,
+ DB: sqlStore,
+ Tracer: tracingService,
+ Reg: registerer,
+ Authzc: accessClient,
+ Docs: documentBuilderSupplier,
+ }
+ storageMetrics := resource.ProvideStorageMetrics(registerer)
+ bleveIndexMetrics := resource.ProvideIndexMetrics(registerer)
+ resourceClient, err := unified.ProvideUnifiedStorageClient(options, storageMetrics, bleveIndexMetrics)
+ if err != nil {
+ return nil, err
+ }
+ dualwriteService := dualwrite.ProvideService(featureToggles, registerer, kvStore, cfg)
+ sortService := sort.ProvideService()
+ folderimplService := folderimpl.ProvideService(folderStoreImpl, accessControl, inProcBus, dashboardsStore, dashboardFolderStoreImpl, userService, sqlStore, featureToggles, bundleregistryService, publicDashboardServiceWrapperImpl, cfg, registerer, tracer, resourceClient, dualwriteService, sortService, eventualRestConfigProvider)
+ searchService := searchV2.ProvideService(cfg, sqlStore, entityEventsService, acimplService, tracingService, featureToggles, orgService, userService, folderimplService)
+ systemUsers := store.ProvideSystemUsersService()
+ storageService, err := store.ProvideService(sqlStore, featureToggles, cfg, quotaService, systemUsers)
+ if err != nil {
+ return nil, err
+ }
+ grafanadsService := grafanads.ProvideService(searchService, storageService, featureToggles)
+ pyroscopeService := pyroscope.ProvideService(httpclientProvider)
+ parcaService := parca.ProvideService(httpclientProvider)
+ zipkinService := zipkin.ProvideService(httpclientProvider)
+ jaegerService := jaeger.ProvideService(httpclientProvider)
+ corepluginRegistry := coreplugin.ProvideCoreRegistry(tracingService, azuremonitorService, cloudwatchService, cloudmonitoringService, elasticsearchService, graphiteService, influxdbService, lokiService, opentsdbService, prometheusService, tempoService, testdatasourceService, postgresService, mysqlService, mssqlService, grafanadsService, pyroscopeService, parcaService, zipkinService, jaegerService)
+ providerService := provider2.ProvideService(corepluginRegistry)
+ processService := process.ProvideService()
+ retrieverService := retriever.ProvideService(sqlStore, apikeyService, kvStore, userService, orgService)
+ serviceAccountPermissionsService, err := ossaccesscontrol.ProvideServiceAccountPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, retrieverService, acimplService, teamService, userService, actionSetService)
+ if err != nil {
+ return nil, err
+ }
+ serviceAccountsService, err := manager2.ProvideServiceAccountsService(cfg, usageStats, sqlStore, apikeyService, kvStore, userService, orgService, acimplService, serviceAccountPermissionsService, serverLockService)
+ if err != nil {
+ return nil, err
+ }
+ extSvcAccountsService := extsvcaccounts.ProvideExtSvcAccountsService(acimplService, cfg, inProcBus, sqlStore, featureToggles, registerer, serviceAccountsService, secretsService, tracingService)
+ registryRegistry := registry2.ProvideExtSvcRegistry(cfg, extSvcAccountsService, serverLockService, featureToggles)
+ service11 := service4.ProvideService(sqlStore, secretsService)
+ serviceregistrationService := serviceregistration.ProvideService(cfg, featureToggles, registryRegistry, service11)
+ initialize := pipeline.ProvideInitializationStage(pluginManagementCfg, inMemory, providerService, processService, serviceregistrationService, acimplService, actionSetService, envVarsProvider, tracingService)
+ terminate, err := pipeline.ProvideTerminationStage(pluginManagementCfg, inMemory, processService)
+ if err != nil {
+ return nil, err
+ }
+ errorRegistry := pluginerrs.ProvideErrorTracker()
+ loaderLoader := loader.ProvideService(pluginManagementCfg, discovery, bootstrap, validate, initialize, terminate, errorRegistry)
+ pluginstoreService, err := pluginstore.ProvideService(inMemory, sourcesService, loaderLoader)
+ if err != nil {
+ return nil, err
+ }
+ filestoreService := filestore.ProvideService(inMemory)
+ fileStoreManager := dashboards.ProvideFileStoreManager(pluginstoreService, filestoreService)
+ folderPermissionsService, err := ossaccesscontrol.ProvideFolderPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, folderimplService, acimplService, teamService, userService, actionSetService)
+ if err != nil {
+ return nil, err
+ }
+ dashboardServiceImpl, err := service5.ProvideDashboardServiceImpl(cfg, dashboardsStore, dashboardFolderStoreImpl, featureToggles, folderPermissionsService, accessControl, acimplService, folderimplService, registerer, eventualRestConfigProvider, userService, quotaService, orgService, publicDashboardServiceWrapperImpl, resourceClient, dualwriteService, sortService, serverLockService, kvStore)
+ if err != nil {
+ return nil, err
+ }
+ pluginService := service5.ProvideDashboardPluginService(featureToggles, dashboardServiceImpl)
+ service12 := service6.ProvideService(fileStoreManager, pluginService)
+ oauthtokentestService := oauthtokentest.ProvideService()
+ ossCachingService := caching.ProvideCachingService()
+ middlewareHandler, err := pluginsintegration.ProvideClientWithMiddlewares(cfg, inMemory, oauthtokentestService, tracingService, ossCachingService, featureToggles, registerer)
+ if err != nil {
+ return nil, err
+ }
+ pluginerrsStore := pluginerrs.ProvideStore(errorRegistry)
+ repoManager, err := repo.ProvideService(pluginManagementCfg)
+ if err != nil {
+ return nil, err
+ }
+ pluginInstaller := manager3.ProvideInstaller(pluginManagementCfg, inMemory, loaderLoader, repoManager, serviceregistrationService)
+ ossProvider := guardian.ProvideGuardian()
+ cacheServiceImpl := service7.ProvideCacheService(cacheService, sqlStore, ossProvider)
+ userAuthTokenService, err := authimpl.ProvideUserAuthTokenService(sqlStore, serverLockService, quotaService, secretsService, cfg, tracingService, featureToggles)
+ if err != nil {
+ return nil, err
+ }
+ shortURLService := shorturlimpl.ProvideService(sqlStore)
+ queryHistoryService := queryhistory.ProvideService(cfg, sqlStore, routeRegisterImpl, accessControl)
+ dashboardService := service5.ProvideDashboardService(featureToggles, dashboardServiceImpl)
+ dashverService := dashverimpl.ProvideService(cfg, sqlStore, dashboardService, dashboardsStore, featureToggles, eventualRestConfigProvider, userService, resourceClient, dualwriteService, sortService)
+ dashboardSnapshotStore := database4.ProvideStore(sqlStore, cfg)
+ serviceImpl := service8.ProvideService(dashboardSnapshotStore, secretsService, dashboardService)
+ dBstore, err := store2.ProvideDBStore(cfg, featureToggles, sqlStore, folderimplService, dashboardService, accessControl, inProcBus)
+ if err != nil {
+ return nil, err
+ }
+ deleteExpiredService := image.ProvideDeleteExpiredService(dBstore)
+ tempuserService := tempuserimpl.ProvideService(sqlStore, cfg)
+ cleanupServiceImpl := annotationsimpl.ProvideCleanupService(sqlStore, cfg)
+ cleanUpService := cleanup.ProvideService(cfg, serverLockService, shortURLService, sqlStore, queryHistoryService, dashverService, serviceImpl, deleteExpiredService, tempuserService, tracingService, cleanupServiceImpl, dashboardService, dBstore)
+ secretsKVStore, err := kvstore2.ProvideService(sqlStore, secretsService)
+ if err != nil {
+ return nil, err
+ }
+ datasourcePermissionsService := ossaccesscontrol.ProvideDatasourcePermissionsService(cfg, featureToggles, sqlStore)
+ requestConfigProvider := pluginconfig.NewRequestConfigProvider(pluginInstanceCfg)
+ baseProvider := plugincontext.ProvideBaseService(cfg, requestConfigProvider)
+ service13, err := service7.ProvideService(sqlStore, secretsService, secretsKVStore, cfg, featureToggles, accessControl, datasourcePermissionsService, quotaService, pluginstoreService, middlewareHandler, baseProvider)
+ if err != nil {
+ return nil, err
+ }
+ correlationsService, err := correlations.ProvideService(sqlStore, routeRegisterImpl, service13, accessControl, inProcBus, quotaService, cfg)
+ if err != nil {
+ return nil, err
+ }
+ mailer, err := notifications.ProvideSmtpService(cfg)
+ if err != nil {
+ return nil, err
+ }
+ notificationService, err := notifications.ProvideService(inProcBus, cfg, mailer, tempuserService)
+ if err != nil {
+ return nil, err
+ }
+ dashboardProvisioningService := service5.ProvideDashboardProvisioningService(featureToggles, dashboardServiceImpl)
+ receiverPermissionsService, err := ossaccesscontrol.ProvideReceiverPermissionsService(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService)
+ if err != nil {
+ return nil, err
+ }
+ provisioningServiceImpl, err := provisioning.ProvideService(accessControl, cfg, sqlStore, pluginstoreService, dBstore, serviceService, notificationService, dashboardProvisioningService, service13, correlationsService, dashboardService, folderimplService, service11, searchService, quotaService, secretsService, orgService, receiverPermissionsService, tracingService, dualwriteService)
+ if err != nil {
+ return nil, err
+ }
+ orgRoleMapper := connectors.ProvideOrgRoleMapper(cfg, orgService)
+ ssosettingsimplService := ssosettingsimpl.ProvideService(cfg, sqlStore, accessControl, routeRegisterImpl, featureToggles, secretsService, usageStats, registerer, ossImpl, ossLicensingService)
+ socialService := socialimpl.ProvideService(cfg, featureToggles, usageStats, bundleregistryService, remoteCache, orgRoleMapper, ssosettingsimplService)
+ loginStore := authinfoimpl.ProvideStore(sqlStore, secretsService)
+ authinfoimplService := authinfoimpl.ProvideService(loginStore, remoteCache, secretsService)
+ oauthtokenService := oauthtoken.ProvideService(socialService, authinfoimplService, cfg, registerer, serverLockService, tracingService, userAuthTokenService, featureToggles)
+ dataSourceProxyService := datasourceproxy.ProvideService(cacheServiceImpl, ossDataSourceRequestValidator, pluginstoreService, cfg, httpclientProvider, oauthtokenService, service13, tracingService, secretsService, featureToggles)
+ starService := starimpl.ProvideService(sqlStore)
+ searchSearchService := search2.ProvideService(cfg, sqlStore, starService, dashboardService, folderimplService, featureToggles, sortService)
+ plugincontextProvider := plugincontext.ProvideService(cfg, cacheService, pluginstoreService, cacheServiceImpl, service13, service11, requestConfigProvider)
+ exprService := expr.ProvideService(cfg, middlewareHandler, plugincontextProvider, featureToggles, registerer, tracingService)
+ queryServiceImpl := query.ProvideService(cfg, cacheServiceImpl, exprService, ossDataSourceRequestValidator, middlewareHandler, plugincontextProvider)
+ repositoryImpl := annotationsimpl.ProvideService(sqlStore, cfg, featureToggles, tagimplService, tracingService, dBstore, dashboardService, registerer)
+ grafanaLive, err := live.ProvideService(plugincontextProvider, cfg, routeRegisterImpl, pluginstoreService, middlewareHandler, cacheService, cacheServiceImpl, sqlStore, secretsService, usageStats, queryServiceImpl, featureToggles, accessControl, dashboardService, repositoryImpl, orgService, eventualRestConfigProvider)
+ if err != nil {
+ return nil, err
+ }
+ gateway := pushhttp.ProvideService(cfg, grafanaLive)
+ authnimplService := authnimpl.ProvideService(cfg, tracingService, userAuthTokenService, usageStats, registerer, authinfoimplService)
+ authnAuthenticator := authnimpl.ProvideAuthnServiceAuthenticateOnly(authnimplService)
+ contexthandlerContextHandler := contexthandler.ProvideService(cfg, authnAuthenticator, featureToggles)
+ logger := loggermw.Provide(cfg, featureToggles)
+ notificationServiceMock := notifications.MockNotificationService()
+ ngAlert := metrics2.ProvideServiceForTest()
+ alertNG, err := ngalert.ProvideService(cfg, featureToggles, cacheServiceImpl, service13, routeRegisterImpl, sqlStore, kvStore, exprService, dataSourceProxyService, quotaService, secretsService, notificationServiceMock, ngAlert, folderimplService, accessControl, dashboardService, renderingService, inProcBus, acimplService, repositoryImpl, pluginstoreService, tracingService, dBstore, httpclientProvider, plugincontextProvider, receiverPermissionsService, userService)
+ if err != nil {
+ return nil, err
+ }
+ libraryElementService := libraryelements.ProvideService(cfg, sqlStore, routeRegisterImpl, folderimplService, featureToggles, accessControl, dashboardService, eventualRestConfigProvider, userService)
+ libraryPanelService, err := librarypanels.ProvideService(cfg, sqlStore, routeRegisterImpl, libraryElementService, folderimplService)
+ if err != nil {
+ return nil, err
+ }
+ grafanaService, err := updatemanager.ProvideGrafanaService(cfg, tracingService)
+ if err != nil {
+ return nil, err
+ }
+ noop := managedplugins.NewNoop()
+ provisionedpluginsNoop := provisionedplugins.NewNoop()
+ preinstallImpl := pluginchecker.ProvidePreinstall(cfg)
+ plugincheckerService := pluginchecker.ProvideService(noop, provisionedpluginsNoop, preinstallImpl)
+ pluginsService, err := updatemanager.ProvidePluginsService(cfg, pluginstoreService, pluginInstaller, tracingService, featureToggles, plugincheckerService)
+ if err != nil {
+ return nil, err
+ }
+ ossSearchUserFilter := filters.ProvideOSSSearchUserFilter()
+ ossService := searchusers.ProvideUsersService(cfg, ossSearchUserFilter, userService)
+ serviceAccountsProxy, err := proxy.ProvideServiceAccountsProxy(cfg, accessControl, acimplService, featureToggles, serviceAccountPermissionsService, serviceAccountsService, routeRegisterImpl)
+ if err != nil {
+ return nil, err
+ }
+ pluginassetsService := pluginassets.ProvideService(pluginManagementCfg, pluginscdnService, signatureSignature, pluginstoreService)
+ avatarCacheServer := avatar.ProvideAvatarCacheServer(cfg)
+ prefService := prefimpl.ProvideService(sqlStore, cfg)
+ dashboardPermissionsService, err := ossaccesscontrol.ProvideDashboardPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, dashboardService, folderimplService, acimplService, teamService, userService, actionSetService, dashboardServiceImpl)
+ if err != nil {
+ return nil, err
+ }
+ csrfCSRF := csrf.ProvideCSRFFilter(cfg)
+ playlistService := playlistimpl.ProvideService(sqlStore, tracingService)
+ secretsMigrator := migrator.ProvideSecretsMigrator(serviceService, secretsService, sqlStore, ossImpl, featureToggles)
+ dataSourceSecretMigrationService := migrations2.ProvideDataSourceMigrationService(service13, kvStore, featureToggles)
+ secretMigrationProviderImpl := migrations2.ProvideSecretMigrationProvider(serverLockService, dataSourceSecretMigrationService)
+ publicDashboardServiceImpl := service3.ProvideService(cfg, featureToggles, publicDashboardStoreImpl, queryServiceImpl, repositoryImpl, accessControl, publicDashboardServiceWrapperImpl, dashboardService, ossLicensingService)
+ middleware := api2.ProvideMiddleware()
+ apiApi := api2.ProvideApi(publicDashboardServiceImpl, routeRegisterImpl, accessControl, featureToggles, middleware, cfg, ossLicensingService)
+ loginattemptimplService := loginattemptimpl.ProvideService(sqlStore, cfg, serverLockService)
+ deletionService, err := orgimpl.ProvideDeletionService(sqlStore, cfg, dashboardService, accessControl)
+ if err != nil {
+ return nil, err
+ }
+ authnService := authnimpl.ProvideAuthnService(authnimplService)
+ openFeatureService, err := featuremgmt.ProvideOpenFeatureService(cfg)
+ if err != nil {
+ return nil, err
+ }
+ navtreeService := navtreeimpl.ProvideService(cfg, accessControl, pluginstoreService, service11, starService, featureToggles, dashboardService, acimplService, kvStore, apikeyService, ossLicensingService, authnService, openFeatureService)
+ searchHTTPService := searchV2.ProvideSearchHTTPService(searchService)
+ statsService := statsimpl.ProvideService(cfg, sqlStore, dashboardService, folderimplService, orgService, featureToggles)
+ gatherer := metrics.ProvideGathererForTest(registerer)
+ apiAPI := api3.ProvideApi(starService, dashboardService)
+ anonUserLimitValidatorImpl := validator.ProvideAnonUserLimitValidator()
+ anonDeviceService := anonimpl.ProvideAnonymousDeviceService(usageStats, authnService, sqlStore, cfg, orgService, serverLockService, accessControl, routeRegisterImpl, anonUserLimitValidatorImpl)
+ signingkeysimplService, err := signingkeysimpl.ProvideEmbeddedSigningKeysService(sqlStore, secretsService, remoteCache, routeRegisterImpl)
+ if err != nil {
+ return nil, err
+ }
+ localSigner, err := idimpl.ProvideLocalSigner(signingkeysimplService)
+ if err != nil {
+ return nil, err
+ }
+ idimplService := idimpl.ProvideService(cfg, localSigner, remoteCache, authnService, registerer)
+ verifier := userimpl.ProvideVerifier(cfg, userService, tempuserService, notificationServiceMock, idimplService)
+ httpServer, err := api.ProvideHTTPServer(apiOpts, cfg, routeRegisterImpl, inProcBus, renderingService, ossLicensingService, hooksService, cacheService, sqlStore, ossDataSourceRequestValidator, pluginstoreService, service12, pluginstoreService, middlewareHandler, pluginerrsStore, pluginInstaller, ossImpl, cacheServiceImpl, userAuthTokenService, cleanUpService, shortURLService, queryHistoryService, correlationsService, remoteCache, provisioningServiceImpl, accessControl, dataSourceProxyService, searchSearchService, grafanaLive, gateway, plugincontextProvider, contexthandlerContextHandler, logger, featureToggles, alertNG, libraryPanelService, libraryElementService, quotaService, socialService, tracingService, serviceService, grafanaService, pluginsService, ossService, service13, queryServiceImpl, filestoreService, serviceAccountsProxy, pluginassetsService, authinfoimplService, storageService, notificationServiceMock, dashboardService, dashboardProvisioningService, folderimplService, ossProvider, serviceImpl, service11, avatarCacheServer, prefService, folderPermissionsService, dashboardPermissionsService, dashverService, starService, csrfCSRF, noop, playlistService, apikeyService, kvStore, secretsMigrator, secretsService, secretMigrationProviderImpl, secretsKVStore, apiApi, userService, tempuserService, loginattemptimplService, orgService, deletionService, teamService, acimplService, navtreeService, repositoryImpl, tagimplService, searchHTTPService, oauthtokentestService, statsService, authnService, pluginscdnService, gatherer, apiAPI, registerer, eventualRestConfigProvider, anonDeviceService, verifier, preinstallImpl)
+ if err != nil {
+ return nil, err
+ }
+ validatorService, err := validator2.ProvideService(pluginstoreService)
+ if err != nil {
+ return nil, err
+ }
+ sandboxService := sandbox.ProvideService(cfg)
+ advisorService, err := advisor.ProvideService(cfg, eventualRestConfigProvider)
+ if err != nil {
+ return nil, err
+ }
+ statscollectorService := statscollector.ProvideService(usageStats, validatorService, statsService, cfg, sqlStore, socialService, pluginstoreService, featureManager, service13, httpclientProvider, sandboxService, advisorService)
+ internalMetricsService, err := metrics.ProvideService(cfg, registerer, gatherer)
+ if err != nil {
+ return nil, err
+ }
+ supportbundlesimplService, err := supportbundlesimpl.ProvideService(accessControl, acimplService, bundleregistryService, cfg, featureToggles, httpServer, kvStore, service11, pluginstoreService, routeRegisterImpl, ossImpl, sqlStore, usageStats, tracingService)
+ if err != nil {
+ return nil, err
+ }
+ metricService, err := metric.ProvideService(publicDashboardStoreImpl, registerer)
+ if err != nil {
+ return nil, err
+ }
+ scopedPluginDatasourceProvider := datasource.ProvideDefaultPluginConfigs(service13, cacheServiceImpl, plugincontextProvider)
+ v := builder.ProvideDefaultBuildHandlerChainFuncFromBuilders()
+ aggregatorRunner := aggregatorrunner.ProvideNoopAggregatorConfigurator()
+ apiserverService, err := apiserver.ProvideService(cfg, featureToggles, routeRegisterImpl, tracingService, serverLockService, sqlStore, kvStore, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, dualwriteService, resourceClient, eventualRestConfigProvider, v, eventualRestConfigProvider, registerer, aggregatorRunner)
+ if err != nil {
+ return nil, err
+ }
+ pluginexternalService, err := pluginexternal.ProvideService(cfg, pluginstoreService)
+ if err != nil {
+ return nil, err
+ }
+ plugininstallerService, err := plugininstaller.ProvideService(cfg, pluginstoreService, pluginInstaller, registerer, repoManager, featureToggles, plugincheckerService)
+ if err != nil {
+ return nil, err
+ }
+ zanzanaReconciler := dualwrite2.ProvideZanzanaReconciler(cfg, featureToggles, client, sqlStore, serverLockService, folderimplService)
+ playlistAppProvider := playlist.RegisterApp(playlistService, cfg, featureToggles)
+ investigationsAppProvider := investigations.RegisterApp(cfg)
+ checkregistryService := checkregistry.ProvideService(service13, pluginstoreService, plugincontextProvider, middlewareHandler, plugincheckerService, repoManager, preinstallImpl, noop, provisionedpluginsNoop, ssosettingsimplService, cfg, pluginerrsStore)
+ advisorAppProvider := advisor2.RegisterApp(checkregistryService, cfg)
+ alertingNotificationsAppProvider := notifications2.RegisterApp(cfg, alertNG)
+ appregistryService, err := appregistry.ProvideRegistryServiceSink(apiserverService, eventualRestConfigProvider, featureToggles, playlistAppProvider, investigationsAppProvider, advisorAppProvider, alertingNotificationsAppProvider, cfg)
+ if err != nil {
+ return nil, err
+ }
+ importDashboardService := service9.ProvideService(routeRegisterImpl, quotaService, service12, pluginstoreService, libraryPanelService, dashboardService, accessControl, folderimplService, featureToggles)
+ dashboardUpdater := service6.ProvideDashboardUpdater(inProcBus, pluginstoreService, service12, importDashboardService, service11, pluginService, dashboardService)
+ sanitizerProvider := sanitizer.ProvideService(renderingService)
+ healthService, err := grpcserver.ProvideHealthService(cfg, grpcserverProvider)
+ if err != nil {
+ return nil, err
+ }
+ reflectionService, err := grpcserver.ProvideReflectionService(cfg, grpcserverProvider)
+ if err != nil {
+ return nil, err
+ }
+ ossGroups := ldap.ProvideGroupsService()
+ identitySynchronizer := authnimpl.ProvideIdentitySynchronizer(authnimplService)
+ ldapImpl := service10.ProvideService(cfg, featureToggles, ssosettingsimplService)
+ apiService := api4.ProvideService(cfg, routeRegisterImpl, accessControl, userService, authinfoimplService, ossGroups, identitySynchronizer, orgService, ldapImpl, userAuthTokenService, bundleregistryService)
+ dashboardsAPIBuilder := dashboard.RegisterAPIService(cfg, featureToggles, apiserverService, dashboardService, dashboardProvisioningService, service13, dashboardServiceImpl, accessControl, accessClient, provisioningServiceImpl, dashboardsStore, registerer, sqlStore, tracingService, resourceClient, dualwriteService, sortService, quotaService, dashboardFolderStoreImpl, libraryPanelService, eventualRestConfigProvider, userService)
+ snapshotsAPIBuilder := dashboardsnapshot.RegisterAPIService(serviceImpl, apiserverService, cfg, featureToggles, sqlStore, registerer)
+ featureFlagAPIBuilder := featuretoggle.RegisterAPIService(featureManager, accessControl, apiserverService, cfg, registerer)
+ dataSourceAPIBuilder, err := datasource.RegisterAPIService(featureToggles, apiserverService, middlewareHandler, scopedPluginDatasourceProvider, plugincontextProvider, pluginstoreService, accessControl, registerer)
+ if err != nil {
+ return nil, err
+ }
+ folderAPIBuilder := folders.RegisterAPIService(cfg, featureToggles, apiserverService, folderimplService, folderPermissionsService, accessControl, acimplService, registerer, resourceClient)
+ storageBackendImpl := noopstorage.ProvideStorageBackend()
+ identityAccessManagementAPIBuilder, err := iam.RegisterAPIService(featureToggles, apiserverService, ssosettingsimplService, sqlStore, accessControl, accessClient, registerer, storageBackendImpl)
+ if err != nil {
+ return nil, err
+ }
+ legacyDataSourceLookup := service7.ProvideLegacyDataSourceLookup(service13)
+ queryAPIBuilder, err := query2.RegisterAPIService(featureToggles, apiserverService, service13, pluginstoreService, accessControl, middlewareHandler, plugincontextProvider, registerer, tracingService, legacyDataSourceLookup)
+ if err != nil {
+ return nil, err
+ }
+ userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer)
+ databaseDatabase := database5.ProvideDatabase(sqlStore)
+ secureValueMetadataStorage, err := metadata.ProvideSecureValueMetadataStorage(databaseDatabase, featureToggles)
+ if err != nil {
+ return nil, err
+ }
+ keeperMetadataStorage, err := metadata.ProvideKeeperMetadataStorage(databaseDatabase, featureToggles)
+ if err != nil {
+ return nil, err
+ }
+ secretDBMigrator := migrator2.NewWithEngine(sqlStore)
+ secretAPIBuilder, err := secret.RegisterAPIService(featureToggles, cfg, apiserverService, tracingService, secureValueMetadataStorage, keeperMetadataStorage, accessClient, acimplService, secretDBMigrator)
+ if err != nil {
+ return nil, err
+ }
+ factory := github.ProvideFactory()
+ legacyMigrator := legacy.ProvideLegacyMigrator(sqlStore, provisioningServiceImpl, libraryPanelService, accessControl)
+ webhookExtraBuilder := webhooks.ProvideWebhooks(cfg, featureToggles, secretsService, factory, renderingService, resourceClient, eventualRestConfigProvider)
+ v2 := apiregistry.MergeProvisioningExtras(webhookExtraBuilder)
+ apiBuilder, err := provisioning2.RegisterAPIService(cfg, featureToggles, apiserverService, registerer, resourceClient, eventualRestConfigProvider, factory, accessClient, legacyMigrator, dualwriteService, usageStats, secretsService, v2)
+ if err != nil {
+ return nil, err
+ }
+ staticFlagEvaluator, err := featuremgmt.ProvideStaticEvaluator(cfg)
+ if err != nil {
+ return nil, err
+ }
+ ofrepAPIBuilder := ofrep.RegisterAPIService(apiserverService, cfg, staticFlagEvaluator)
+ apiregistryService := apiregistry.ProvideRegistryServiceSink(dashboardsAPIBuilder, snapshotsAPIBuilder, featureFlagAPIBuilder, dataSourceAPIBuilder, folderAPIBuilder, identityAccessManagementAPIBuilder, queryAPIBuilder, userStorageAPIBuilder, secretAPIBuilder, apiBuilder, ofrepAPIBuilder)
+ teamPermissionsService, err := ossaccesscontrol.ProvideTeamPermissions(cfg, featureToggles, routeRegisterImpl, sqlStore, accessControl, ossLicensingService, acimplService, teamService, userService, actionSetService)
+ if err != nil {
+ return nil, err
+ }
+ teamAPI := teamapi.ProvideTeamAPI(routeRegisterImpl, teamService, acimplService, accessControl, teamPermissionsService, userService, ossLicensingService, cfg, prefService, dashboardService, featureToggles)
+ cloudmigrationService, err := cloudmigrationimpl.ProvideService(cfg, httpclientProvider, featureToggles, sqlStore, service13, secretsKVStore, secretsService, routeRegisterImpl, registerer, tracingService, dashboardService, folderimplService, pluginstoreService, service11, accessControl, acimplService, kvStore, libraryElementService, alertNG)
+ if err != nil {
+ return nil, err
+ }
+ authService, err := jwt.ProvideService(cfg, remoteCache)
+ if err != nil {
+ return nil, err
+ }
+ ossUserProtectionImpl := authinfoimpl.ProvideOSSUserProtectionService()
+ registration := authnimpl.ProvideRegistration(cfg, authnService, orgService, userAuthTokenService, acimplService, permissionRegistry, apikeyService, userService, authService, ossUserProtectionImpl, loginattemptimplService, quotaService, authinfoimplService, renderingService, featureToggles, oauthtokentestService, socialService, remoteCache, ldapImpl, ossImpl, tracingService, tempuserService, notificationServiceMock)
+ backgroundServiceRegistry := backgroundsvcs.ProvideBackgroundServiceRegistry(httpServer, alertNG, cleanUpService, grafanaLive, gateway, notificationService, pluginstoreService, renderingService, userAuthTokenService, tracingService, provisioningServiceImpl, usageStats, statscollectorService, grafanaService, pluginsService, internalMetricsService, secretsService, remoteCache, storageService, searchService, entityEventsService, serviceAccountsService, grpcserverProvider, secretMigrationProviderImpl, loginattemptimplService, supportbundlesimplService, metricService, keyRetriever, angulardetectorsproviderDynamic, apiserverService, anonDeviceService, ssosettingsimplService, pluginexternalService, plugininstallerService, zanzanaReconciler, appregistryService, dashboardUpdater, dashboardServiceImpl, serviceImpl, serviceAccountsProxy, sanitizerProvider, healthService, reflectionService, apiService, apiregistryService, idimplService, teamAPI, ssosettingsimplService, cloudmigrationService, registration)
+ usageStatsProvidersRegistry := usagestatssvcs.ProvideUsageStatsProvidersRegistry(acimplService, userService)
+ server, err := New(opts, cfg, httpServer, acimplService, provisioningServiceImpl, backgroundServiceRegistry, usageStatsProvidersRegistry, statscollectorService, registerer)
+ if err != nil {
+ return nil, err
+ }
+ testEnv, err := ProvideTestEnv(testingT, server, sqlStore, cfg, notificationServiceMock, grpcserverProvider, inMemory, httpclientProvider, oauthtokentestService, featureToggles, resourceClient, idimplService, factory)
+ if err != nil {
+ return nil, err
+ }
+ return testEnv, nil
+}
+
+func InitializeForCLI(cfg *setting.Cfg) (Runner, error) {
+ featureManager, err := featuremgmt.ProvideManagerService(cfg)
+ if err != nil {
+ return Runner{}, err
+ }
+ featureToggles := featuremgmt.ProvideToggles(featureManager)
+ ossMigrations := migrations.ProvideOSSMigrations(featureToggles)
+ tracingConfig, err := tracing.ProvideTracingConfig(cfg)
+ if err != nil {
+ return Runner{}, err
+ }
+ tracingService, err := tracing.ProvideService(tracingConfig)
+ if err != nil {
+ return Runner{}, err
+ }
+ inProcBus := bus.ProvideBus(tracingService)
+ sqlStore, err := sqlstore.ProvideService(cfg, featureToggles, ossMigrations, inProcBus, tracingService)
+ if err != nil {
+ return Runner{}, err
+ }
+ ossImpl := setting.ProvideProvider(cfg)
+ providerProvider := provider.ProvideEncryptionProvider()
+ kvStore := kvstore.ProvideService(sqlStore)
+ routeRegisterImpl := routing.ProvideRegister()
+ accessControl := acimpl.ProvideAccessControl(featureToggles)
+ bundleregistryService := bundleregistry.ProvideService()
+ usageStats, err := service.ProvideService(cfg, kvStore, routeRegisterImpl, tracingService, accessControl, bundleregistryService)
+ if err != nil {
+ return Runner{}, err
+ }
+ serviceService, err := service2.ProvideEncryptionService(tracingService, providerProvider, usageStats, cfg)
+ if err != nil {
+ return Runner{}, err
+ }
+ secretsStoreImpl := database.ProvideSecretsStore(sqlStore)
+ osskmsprovidersService := osskmsproviders.ProvideService(serviceService, cfg, featureToggles)
+ secretsService, err := manager.ProvideSecretsService(tracingService, secretsStoreImpl, osskmsprovidersService, serviceService, cfg, featureToggles, usageStats)
+ if err != nil {
+ return Runner{}, err
+ }
+ secretsMigrator := migrator.ProvideSecretsMigrator(serviceService, secretsService, sqlStore, ossImpl, featureToggles)
+ quotaService := quotaimpl.ProvideService(sqlStore, cfg)
+ orgService, err := orgimpl.ProvideService(sqlStore, cfg, quotaService)
+ if err != nil {
+ return Runner{}, err
+ }
+ teamService, err := teamimpl.ProvideService(sqlStore, cfg, tracingService)
+ if err != nil {
+ return Runner{}, err
+ }
+ cacheService := localcache.ProvideService()
+ userService, err := userimpl.ProvideService(sqlStore, orgService, cfg, teamService, cacheService, tracingService, quotaService, bundleregistryService)
+ if err != nil {
+ return Runner{}, err
+ }
+ runner := NewRunner(cfg, sqlStore, ossImpl, serviceService, featureToggles, secretsService, secretsMigrator, userService)
+ return runner, nil
+}
+
+// InitializeForCLITarget is a simplified set of dependencies for the CLI, used
+// by the server target subcommand to launch specific dskit modules.
+func InitializeForCLITarget(cfg *setting.Cfg) (ModuleRunner, error) {
+ ossImpl := setting.ProvideProvider(cfg)
+ featureManager, err := featuremgmt.ProvideManagerService(cfg)
+ if err != nil {
+ return ModuleRunner{}, err
+ }
+ featureToggles := featuremgmt.ProvideToggles(featureManager)
+ moduleRunner := NewModuleRunner(cfg, ossImpl, featureToggles)
+ return moduleRunner, nil
+}
+
+// InitializeModuleServer is a simplified set of dependencies for the CLI,
+// suitable for running background services and targeting dskit modules.
+func InitializeModuleServer(cfg *setting.Cfg, opts Options, apiOpts api.ServerOptions) (*ModuleServer, error) {
+ featureManager, err := featuremgmt.ProvideManagerService(cfg)
+ if err != nil {
+ return nil, err
+ }
+ featureToggles := featuremgmt.ProvideToggles(featureManager)
+ registerer := metrics.ProvideRegisterer()
+ storageMetrics := resource.ProvideStorageMetrics(registerer)
+ bleveIndexMetrics := resource.ProvideIndexMetrics(registerer)
+ gatherer := metrics.ProvideGatherer()
+ hooksService := hooks.ProvideService()
+ ossLicensingService := licensing.ProvideService(cfg, hooksService)
+ moduleServer, err := NewModule(opts, apiOpts, featureToggles, cfg, storageMetrics, bleveIndexMetrics, registerer, gatherer, ossLicensingService)
+ if err != nil {
+ return nil, err
+ }
+ return moduleServer, nil
+}
+
+// Initialize the standalone APIServer factory
+func InitializeAPIServerFactory() (standalone.APIServerFactory, error) {
+ apiServerFactory := standalone.ProvideAPIServerFactory()
+ return apiServerFactory, nil
+}
+
+func InitializeDocumentBuilders(cfg *setting.Cfg) (resource.DocumentBuilderSupplier, error) {
+ featureManager, err := featuremgmt.ProvideManagerService(cfg)
+ if err != nil {
+ return nil, err
+ }
+ featureToggles := featuremgmt.ProvideToggles(featureManager)
+ ossMigrations := migrations.ProvideOSSMigrations(featureToggles)
+ tracingConfig, err := tracing.ProvideTracingConfig(cfg)
+ if err != nil {
+ return nil, err
+ }
+ tracingService, err := tracing.ProvideService(tracingConfig)
+ if err != nil {
+ return nil, err
+ }
+ inProcBus := bus.ProvideBus(tracingService)
+ sqlStore, err := sqlstore.ProvideService(cfg, featureToggles, ossMigrations, inProcBus, tracingService)
+ if err != nil {
+ return nil, err
+ }
+ ossDashboardStats := search.ProvideDashboardStats()
+ documentBuilderSupplier := search.ProvideDocumentBuilders(sqlStore, ossDashboardStats)
+ return documentBuilderSupplier, nil
+}
+
+// wire.go:
+
+func otelTracer() trace.Tracer {
+ return otel.GetTracerProvider().Tracer("grafana")
+}
+
+var withOTelSet = wire.NewSet(
+ otelTracer, grpcserver.ProvideService, interceptors.ProvideAuthenticator,
+)
+
+var wireBasicSet = wire.NewSet(annotationsimpl.ProvideService, wire.Bind(new(annotations.Repository), new(*annotationsimpl.RepositoryImpl)), New, api.ProvideHTTPServer, query.ProvideService, wire.Bind(new(query.Service), new(*query.ServiceImpl)), bus.ProvideBus, wire.Bind(new(bus.Bus), new(*bus.InProcBus)), rendering.ProvideService, wire.Bind(new(rendering.Service), new(*rendering.RenderingService)), routing.ProvideRegister, wire.Bind(new(routing.RouteRegister), new(*routing.RouteRegisterImpl)), hooks.ProvideService, kvstore.ProvideService, localcache.ProvideService, bundleregistry.ProvideService, wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)), updatemanager.ProvideGrafanaService, updatemanager.ProvidePluginsService, service.ProvideService, wire.Bind(new(usagestats.Service), new(*service.UsageStats)), validator2.ProvideService, legacy.ProvideLegacyMigrator, pluginsintegration.WireSet, dashboards.ProvideFileStoreManager, wire.Bind(new(dashboards.FileStore), new(*dashboards.FileStoreManager)), cloudwatch.ProvideService, cloudmonitoring.ProvideService, azuremonitor.ProvideService, postgres.ProvideService, mysql.ProvideService, mssql.ProvideService, store.ProvideEntityEventsService, dualwrite.ProvideService, httpclientprovider.New, wire.Bind(new(httpclient.Provider), new(*httpclient2.Provider)), serverlock.ProvideService, annotationsimpl.ProvideCleanupService, wire.Bind(new(annotations.Cleaner), new(*annotationsimpl.CleanupServiceImpl)), cleanup.ProvideService, shorturlimpl.ProvideService, wire.Bind(new(shorturls.Service), new(*shorturlimpl.ShortURLService)), queryhistory.ProvideService, wire.Bind(new(queryhistory.Service), new(*queryhistory.QueryHistoryService)), correlations.ProvideService, wire.Bind(new(correlations.Service), new(*correlations.CorrelationsService)), quotaimpl.ProvideService, remotecache.ProvideService, wire.Bind(new(remotecache.CacheStorage), new(*remotecache.RemoteCache)), authinfoimpl.ProvideService, wire.Bind(new(login.AuthInfoService), new(*authinfoimpl.Service)), authinfoimpl.ProvideStore, datasourceproxy.ProvideService, sort.ProvideService, search2.ProvideService, searchV2.ProvideService, searchV2.ProvideSearchHTTPService, store.ProvideService, store.ProvideSystemUsersService, live.ProvideService, pushhttp.ProvideService, contexthandler.ProvideService, service10.ProvideService, wire.Bind(new(service10.LDAP), new(*service10.LDAPImpl)), jwt.ProvideService, wire.Bind(new(jwt.JWTService), new(*jwt.AuthService)), store2.ProvideDBStore, image.ProvideDeleteExpiredService, ngalert.ProvideService, librarypanels.ProvideService, wire.Bind(new(librarypanels.Service), new(*librarypanels.LibraryPanelService)), libraryelements.ProvideService, wire.Bind(new(libraryelements.Service), new(*libraryelements.LibraryElementService)), notifications.ProvideService, notifications.ProvideSmtpService, github.ProvideFactory, tracing.ProvideService, tracing.ProvideTracingConfig, wire.Bind(new(tracing.Tracer), new(*tracing.TracingService)), withOTelSet, testdatasource.ProvideService, api4.ProvideService, opentsdb.ProvideService, socialimpl.ProvideService, influxdb.ProvideService, wire.Bind(new(social.Service), new(*socialimpl.SocialService)), tempo.ProvideService, loki.ProvideService, graphite.ProvideService, prometheus.ProvideService, elasticsearch.ProvideService, pyroscope.ProvideService, parca.ProvideService, zipkin.ProvideService, jaeger.ProvideService, service7.ProvideCacheService, wire.Bind(new(datasources.CacheService), new(*service7.CacheServiceImpl)), service2.ProvideEncryptionService, wire.Bind(new(encryption.Internal), new(*service2.Service)), manager.ProvideSecretsService, wire.Bind(new(secrets.Service), new(*manager.SecretsService)), database.ProvideSecretsStore, wire.Bind(new(secrets.Store), new(*database.SecretsStoreImpl)), grafanads.ProvideService, wire.Bind(new(dashboardsnapshots.Store), new(*database4.DashboardSnapshotStore)), database4.ProvideStore, wire.Bind(new(dashboardsnapshots.Service), new(*service8.ServiceImpl)), service8.ProvideService, service7.ProvideService, wire.Bind(new(datasources.DataSourceService), new(*service7.Service)), service7.ProvideLegacyDataSourceLookup, retriever.ProvideService, wire.Bind(new(serviceaccounts.ServiceAccountRetriever), new(*retriever.Service)), ossaccesscontrol.ProvideServiceAccountPermissions, wire.Bind(new(accesscontrol.ServiceAccountPermissionsService), new(*ossaccesscontrol.ServiceAccountPermissionsService)), manager2.ProvideServiceAccountsService, proxy.ProvideServiceAccountsProxy, wire.Bind(new(serviceaccounts.Service), new(*proxy.ServiceAccountsProxy)), expr.ProvideService, featuremgmt.ProvideManagerService, featuremgmt.ProvideToggles, featuremgmt.ProvideOpenFeatureService, featuremgmt.ProvideStaticEvaluator, service5.ProvideDashboardServiceImpl, wire.Bind(new(dashboards2.PermissionsRegistrationService), new(*service5.DashboardServiceImpl)), service5.ProvideDashboardService, service5.ProvideDashboardProvisioningService, service5.ProvideDashboardPluginService, database2.ProvideDashboardStore, folderimpl.ProvideService, wire.Bind(new(folder.Service), new(*folderimpl.Service)), folderimpl.ProvideStore, wire.Bind(new(folder.Store), new(*folderimpl.FolderStoreImpl)), folderimpl.ProvideDashboardFolderStore, wire.Bind(new(folder.FolderStore), new(*folderimpl.DashboardFolderStoreImpl)), service9.ProvideService, wire.Bind(new(dashboardimport.Service), new(*service9.ImportDashboardService)), service6.ProvideService, wire.Bind(new(plugindashboards.Service), new(*service6.Service)), service6.ProvideDashboardUpdater, sanitizer.ProvideService, kvstore2.ProvideService, avatar.ProvideAvatarCacheServer, statscollector.ProvideService, csrf.ProvideCSRFFilter, wire.Bind(new(csrf.Service), new(*csrf.CSRF)), ossaccesscontrol.ProvideTeamPermissions, wire.Bind(new(accesscontrol.TeamPermissionsService), new(*ossaccesscontrol.TeamPermissionsService)), ossaccesscontrol.ProvideFolderPermissions, wire.Bind(new(accesscontrol.FolderPermissionsService), new(*ossaccesscontrol.FolderPermissionsService)), ossaccesscontrol.ProvideDashboardPermissions, wire.Bind(new(accesscontrol.DashboardPermissionsService), new(*ossaccesscontrol.DashboardPermissionsService)), ossaccesscontrol.ProvideReceiverPermissionsService, wire.Bind(new(accesscontrol.ReceiverPermissionsService), new(*ossaccesscontrol.ReceiverPermissionsService)), starimpl.ProvideService, playlistimpl.ProvideService, apikeyimpl.ProvideService, dashverimpl.ProvideService, service3.ProvideService, wire.Bind(new(publicdashboards.Service), new(*service3.PublicDashboardServiceImpl)), database3.ProvideStore, wire.Bind(new(publicdashboards.Store), new(*database3.PublicDashboardStoreImpl)), metric.ProvideService, api2.ProvideApi, api3.ProvideApi, userimpl.ProvideService, orgimpl.ProvideService, orgimpl.ProvideDeletionService, statsimpl.ProvideService, grpccontext.ProvideContextHandler, grpcserver.ProvideHealthService, grpcserver.ProvideReflectionService, resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, tempuserimpl.ProvideService, loginattemptimpl.ProvideService, wire.Bind(new(loginattempt.Service), new(*loginattemptimpl.Service)), migrations2.ProvideDataSourceMigrationService, migrations2.ProvideSecretMigrationProvider, wire.Bind(new(migrations2.SecretMigrationProvider), new(*migrations2.SecretMigrationProviderImpl)), resourcepermissions.NewActionSetService, wire.Bind(new(accesscontrol.ActionResolver), new(resourcepermissions.ActionSetService)), wire.Bind(new(pluginaccesscontrol.ActionSetRegistry), new(resourcepermissions.ActionSetService)), permreg.ProvidePermissionRegistry, acimpl.ProvideAccessControl, dualwrite2.ProvideZanzanaReconciler, navtreeimpl.ProvideService, wire.Bind(new(accesscontrol.AccessControl), new(*acimpl.AccessControl)), wire.Bind(new(notifications.TempUserStore), new(tempuser.Service)), tagimpl.ProvideService, wire.Bind(new(tag.Service), new(*tagimpl.Service)), authnimpl.ProvideService, authnimpl.ProvideIdentitySynchronizer, authnimpl.ProvideAuthnService, authnimpl.ProvideAuthnServiceAuthenticateOnly, authnimpl.ProvideRegistration, supportbundlesimpl.ProvideService, extsvcaccounts.ProvideExtSvcAccountsService, wire.Bind(new(serviceaccounts.ExtSvcAccountsService), new(*extsvcaccounts.ExtSvcAccountsService)), registry2.ProvideExtSvcRegistry, wire.Bind(new(extsvcauth.ExternalServiceRegistry), new(*registry2.Registry)), anonstore.ProvideAnonDBStore, wire.Bind(new(anonstore.AnonStore), new(*anonstore.AnonDBStore)), loggermw.Provide, slogadapter.Provide, signingkeysimpl.ProvideEmbeddedSigningKeysService, wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)), ssosettingsimpl.ProvideService, wire.Bind(new(ssosettings.Service), new(*ssosettingsimpl.Service)), idimpl.ProvideService, wire.Bind(new(auth.IDService), new(*idimpl.Service)), cloudmigrationimpl.ProvideService, userimpl.ProvideVerifier, connectors.ProvideOrgRoleMapper, wire.Bind(new(user.Verifier), new(*userimpl.Verifier)), authz.WireSet, metadata.ProvideSecureValueMetadataStorage, metadata.ProvideKeeperMetadataStorage, metadata.ProvideOutboxQueue, encryption2.ProvideDataKeyStorage, encryption2.ProvideEncryptedValueStorage, migrator2.NewWithEngine, database5.ProvideDatabase, wire.Bind(new(contracts.Database), new(*database5.Database)), decrypt.ProvideDecryptAuthorizer, decrypt.ProvideDecryptAllowList, resource.ProvideStorageMetrics, resource.ProvideIndexMetrics, apiserver.WireSet, apiregistry.WireSet, appregistry.WireSet)
+
+var wireSet = wire.NewSet(
+ wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)),
+)
+
+var wireCLISet = wire.NewSet(
+ NewRunner,
+ wireBasicSet, metrics.WireSet, sqlstore.ProvideService, metrics2.ProvideService, wire.Bind(new(notifications.Service), new(*notifications.NotificationService)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationService)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationService)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtoken.Service)),
+)
+
+var wireTestSet = wire.NewSet(
+ wireBasicSet,
+ ProvideTestEnv, metrics.WireSetForTest, sqlstore.ProvideServiceForTests, metrics2.ProvideServiceForTest, notifications.MockNotificationService, wire.Bind(new(notifications.Service), new(*notifications.NotificationServiceMock)), wire.Bind(new(notifications.WebhookSender), new(*notifications.NotificationServiceMock)), wire.Bind(new(notifications.EmailSender), new(*notifications.NotificationServiceMock)), wire.Bind(new(db.DB), new(*sqlstore.SQLStore)), prefimpl.ProvideService, oauthtoken.ProvideService, oauthtokentest.ProvideService, wire.Bind(new(oauthtoken.OAuthTokenService), new(*oauthtokentest.Service)), wire.Bind(new(cleanup.AlertRuleService), new(*store2.DBstore)),
+)
diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go
index 85fc598fad4..417d95f0d52 100644
--- a/pkg/services/featuremgmt/registry.go
+++ b/pkg/services/featuremgmt/registry.go
@@ -697,13 +697,6 @@ var (
Stage: FeatureStageExperimental,
Owner: grafanaDatavizSquad,
},
- {
- Name: "regressionTransformation",
- Description: "Enables regression analysis transformation",
- Stage: FeatureStagePublicPreview,
- FrontendOnly: true,
- Owner: grafanaDatavizSquad,
- },
{
// this is mainly used as a way to quickly disable query hints as a safeguard for our infrastructure
Name: "lokiQueryHints",
@@ -1145,7 +1138,8 @@ var (
{
Name: "improvedExternalSessionHandling",
Description: "Enables improved support for OAuth external sessions. After enabling this feature, users might need to re-authenticate themselves.",
- Stage: FeatureStagePublicPreview,
+ Stage: FeatureStageGeneralAvailability,
+ Expression: "true", // enabled by default
Owner: identityAccessTeam,
AllowSelfServe: true,
},
@@ -1367,7 +1361,8 @@ var (
{
Name: "improvedExternalSessionHandlingSAML",
Description: "Enables improved support for SAML external sessions. Ensure the NameID format is correctly configured in Grafana for SAML Single Logout to function properly.",
- Stage: FeatureStagePublicPreview,
+ Stage: FeatureStageGeneralAvailability,
+ Expression: "true", // enabled by default
Owner: identityAccessTeam,
AllowSelfServe: true,
},
@@ -1675,15 +1670,6 @@ var (
HideFromDocs: true,
Expression: "true", // enabled by default
},
- {
- Name: "extensionsReadOnlyProxy",
- Description: "Use proxy-based read-only objects for plugin extensions instead of deep cloning",
- Stage: FeatureStageExperimental,
- Owner: grafanaPluginsPlatformSquad,
- HideFromAdminPage: true,
- HideFromDocs: true,
- FrontendOnly: true,
- },
{
Name: "kubernetesAuthzApis",
Description: "Registers AuthZ /apis endpoint",
diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv
index 2278cb1fb50..bbd8023623b 100644
--- a/pkg/services/featuremgmt/toggles_gen.csv
+++ b/pkg/services/featuremgmt/toggles_gen.csv
@@ -92,7 +92,6 @@ logsInfiniteScrolling,GA,@grafana/observability-logs,false,false,true
logRowsPopoverMenu,GA,@grafana/observability-logs,false,false,true
pluginsSkipHostEnvVars,experimental,@grafana/plugins-platform-backend,false,false,false
tableSharedCrosshair,experimental,@grafana/dataviz-squad,false,false,true
-regressionTransformation,preview,@grafana/dataviz-squad,false,false,true
lokiQueryHints,GA,@grafana/observability-logs,false,false,true
kubernetesFeatureToggles,experimental,@grafana/grafana-operator-experience-squad,false,false,true
cloudRBACRoles,preview,@grafana/identity-access-team,false,true,false
@@ -148,7 +147,7 @@ exploreLogsLimitedTimeRange,experimental,@grafana/observability-logs,false,false
appPlatformGrpcClientAuth,experimental,@grafana/identity-access-team,false,false,false
groupAttributeSync,privatePreview,@grafana/identity-access-team,false,false,false
alertingQueryAndExpressionsStepMode,GA,@grafana/alerting-squad,false,false,true
-improvedExternalSessionHandling,preview,@grafana/identity-access-team,false,false,false
+improvedExternalSessionHandling,GA,@grafana/identity-access-team,false,false,false
useSessionStorageForRedirection,GA,@grafana/identity-access-team,false,false,false
rolePickerDrawer,experimental,@grafana/identity-access-team,false,false,false
unifiedStorageSearch,experimental,@grafana/search-and-storage,false,false,false
@@ -179,7 +178,7 @@ lokiLabelNamesQueryApi,GA,@grafana/observability-logs,false,false,false
investigationsBackend,experimental,@grafana/grafana-app-platform-squad,false,false,false
k8SFolderCounts,experimental,@grafana/search-and-storage,false,false,false
k8SFolderMove,experimental,@grafana/search-and-storage,false,false,false
-improvedExternalSessionHandlingSAML,preview,@grafana/identity-access-team,false,false,false
+improvedExternalSessionHandlingSAML,GA,@grafana/identity-access-team,false,false,false
teamHttpHeadersMimir,GA,@grafana/identity-access-team,false,false,false
teamHttpHeadersTempo,experimental,@grafana/identity-access-team,false,false,false
templateVariablesUsesCombobox,experimental,@grafana/grafana-frontend-platform,false,false,true
@@ -219,7 +218,6 @@ multiTenantFrontend,experimental,@grafana/grafana-frontend-platform,false,false,
alertingListViewV2PreviewToggle,privatePreview,@grafana/alerting-squad,false,false,true
alertRuleUseFiredAtForStartsAt,experimental,@grafana/alerting-squad,false,false,false
alertingBulkActionsInUI,GA,@grafana/alerting-squad,false,false,true
-extensionsReadOnlyProxy,experimental,@grafana/plugins-platform-backend,false,false,true
kubernetesAuthzApis,experimental,@grafana/identity-access-team,false,false,false
restoreDashboards,experimental,@grafana/grafana-frontend-platform,false,false,false
skipTokenRotationIfRecent,privatePreview,@grafana/identity-access-team,false,false,false
diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go
index fbd6e7ab88a..47d4562df37 100644
--- a/pkg/services/featuremgmt/toggles_gen.go
+++ b/pkg/services/featuremgmt/toggles_gen.go
@@ -379,10 +379,6 @@ const (
// Enables shared crosshair in table panel
FlagTableSharedCrosshair = "tableSharedCrosshair"
- // FlagRegressionTransformation
- // Enables regression analysis transformation
- FlagRegressionTransformation = "regressionTransformation"
-
// FlagLokiQueryHints
// Enables query hints for Loki
FlagLokiQueryHints = "lokiQueryHints"
@@ -887,10 +883,6 @@ const (
// Enables the alerting bulk actions in the UI
FlagAlertingBulkActionsInUI = "alertingBulkActionsInUI"
- // FlagExtensionsReadOnlyProxy
- // Use proxy-based read-only objects for plugin extensions instead of deep cloning
- FlagExtensionsReadOnlyProxy = "extensionsReadOnlyProxy"
-
// FlagKubernetesAuthzApis
// Registers AuthZ /apis endpoint
FlagKubernetesAuthzApis = "kubernetesAuthzApis"
diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json
index dda8e2005c4..ce8ab6502f3 100644
--- a/pkg/services/featuremgmt/toggles_gen.json
+++ b/pkg/services/featuremgmt/toggles_gen.json
@@ -1110,7 +1110,8 @@
"metadata": {
"name": "extensionsReadOnlyProxy",
"resourceVersion": "1750434297879",
- "creationTimestamp": "2025-05-06T04:55:23Z"
+ "creationTimestamp": "2025-05-06T04:55:23Z",
+ "deletionTimestamp": "2025-06-30T08:24:11Z"
},
"spec": {
"description": "Use proxy-based read-only objects for plugin extensions instead of deep cloning",
@@ -1398,27 +1399,35 @@
{
"metadata": {
"name": "improvedExternalSessionHandling",
- "resourceVersion": "1750434297879",
- "creationTimestamp": "2024-09-17T10:54:39Z"
+ "resourceVersion": "1751355094344",
+ "creationTimestamp": "2024-09-17T10:54:39Z",
+ "annotations": {
+ "grafana.app/updatedTimestamp": "2025-07-01 07:31:34.344238 +0000 UTC"
+ }
},
"spec": {
"description": "Enables improved support for OAuth external sessions. After enabling this feature, users might need to re-authenticate themselves.",
- "stage": "preview",
+ "stage": "GA",
"codeowner": "@grafana/identity-access-team",
- "allowSelfServe": true
+ "allowSelfServe": true,
+ "expression": "true"
}
},
{
"metadata": {
"name": "improvedExternalSessionHandlingSAML",
- "resourceVersion": "1750434297879",
- "creationTimestamp": "2025-01-09T17:02:49Z"
+ "resourceVersion": "1751355094344",
+ "creationTimestamp": "2025-01-09T17:02:49Z",
+ "annotations": {
+ "grafana.app/updatedTimestamp": "2025-07-01 07:31:34.344238 +0000 UTC"
+ }
},
"spec": {
"description": "Enables improved support for SAML external sessions. Ensure the NameID format is correctly configured in Grafana for SAML Single Logout to function properly.",
- "stage": "preview",
+ "stage": "GA",
"codeowner": "@grafana/identity-access-team",
- "allowSelfServe": true
+ "allowSelfServe": true,
+ "expression": "true"
}
},
{
@@ -2606,7 +2615,8 @@
"metadata": {
"name": "regressionTransformation",
"resourceVersion": "1750434297879",
- "creationTimestamp": "2023-11-24T14:49:16Z"
+ "creationTimestamp": "2023-11-24T14:49:16Z",
+ "deletionTimestamp": "2025-07-01T13:24:02Z"
},
"spec": {
"description": "Enables regression analysis transformation",
diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go
index c43d27664be..f14dbc03fc7 100644
--- a/pkg/setting/setting.go
+++ b/pkg/setting/setting.go
@@ -567,6 +567,9 @@ type Cfg struct {
IndexRebuildInterval time.Duration
IndexCacheTTL time.Duration
EnableSharding bool
+ QOSEnabled bool
+ QOSNumberWorker int
+ QOSMaxSizePerTenant int
MemberlistBindAddr string
MemberlistAdvertiseAddr string
MemberlistAdvertisePort int
diff --git a/pkg/setting/setting_unified_storage.go b/pkg/setting/setting_unified_storage.go
index 2484d5c1a33..f0881cf95db 100644
--- a/pkg/setting/setting_unified_storage.go
+++ b/pkg/setting/setting_unified_storage.go
@@ -49,13 +49,16 @@ func (cfg *Cfg) setUnifiedStorageConfig() {
}
cfg.UnifiedStorage = storageConfig
- // Set indexer config for unified storaae
+ // Set indexer config for unified storage
section := cfg.Raw.Section("unified_storage")
cfg.MaxPageSizeBytes = section.Key("max_page_size_bytes").MustInt(0)
cfg.IndexPath = section.Key("index_path").String()
cfg.IndexWorkers = section.Key("index_workers").MustInt(10)
cfg.IndexMaxBatchSize = section.Key("index_max_batch_size").MustInt(100)
cfg.EnableSharding = section.Key("enable_sharding").MustBool(false)
+ cfg.QOSEnabled = section.Key("qos_enabled").MustBool(false)
+ cfg.QOSNumberWorker = section.Key("qos_num_worker").MustInt(16)
+ cfg.QOSMaxSizePerTenant = section.Key("qos_max_size_per_tenant").MustInt(1000)
cfg.MemberlistBindAddr = section.Key("memberlist_bind_addr").String()
cfg.MemberlistAdvertiseAddr = section.Key("memberlist_advertise_addr").String()
cfg.MemberlistAdvertisePort = section.Key("memberlist_advertise_port").MustInt(7946)
diff --git a/pkg/storage/unified/client.go b/pkg/storage/unified/client.go
index 1d1855d5eee..53942a903bf 100644
--- a/pkg/storage/unified/client.go
+++ b/pkg/storage/unified/client.go
@@ -20,6 +20,7 @@ import (
"github.com/grafana/dskit/flagext"
"github.com/grafana/dskit/grpcclient"
"github.com/grafana/dskit/middleware"
+ "github.com/grafana/dskit/services"
infraDB "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
@@ -31,6 +32,7 @@ import (
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/sql"
+ "github.com/grafana/grafana/pkg/util/scheduler"
)
type Options struct {
@@ -49,7 +51,10 @@ type clientMetrics struct {
}
// This adds a UnifiedStorage client into the wire dependency tree
-func ProvideUnifiedStorageClient(opts *Options, storageMetrics *resource.StorageMetrics, indexMetrics *resource.BleveIndexMetrics) (resource.ResourceClient, error) {
+func ProvideUnifiedStorageClient(opts *Options,
+ storageMetrics *resource.StorageMetrics,
+ indexMetrics *resource.BleveIndexMetrics,
+) (resource.ResourceClient, error) {
// See: apiserver.applyAPIServerConfig(cfg, features, o)
apiserverCfg := opts.Cfg.SectionWithEnvOverrides("grafana-apiserver")
client, err := newClient(options.StorageOptions{
@@ -83,6 +88,7 @@ func newClient(opts options.StorageOptions,
indexMetrics *resource.BleveIndexMetrics,
) (resource.ResourceClient, error) {
ctx := context.Background()
+
switch opts.StorageType {
case options.StorageTypeFile:
if opts.DataPath == "" {
@@ -146,13 +152,50 @@ func newClient(opts options.StorageOptions,
}
return client, nil
- // Use the local SQL
default:
searchOptions, err := search.NewSearchOptions(features, cfg, tracer, docs, indexMetrics)
if err != nil {
return nil, err
}
- server, err := sql.NewResourceServer(db, cfg, tracer, reg, authzc, searchOptions, storageMetrics, indexMetrics, features)
+
+ serverOptions := sql.ServerOptions{
+ DB: db,
+ Cfg: cfg,
+ Tracer: tracer,
+ Reg: reg,
+ AccessClient: authzc,
+ SearchOptions: searchOptions,
+ StorageMetrics: storageMetrics,
+ IndexMetrics: indexMetrics,
+ Features: features,
+ }
+
+ if cfg.QOSEnabled {
+ qosReg := prometheus.WrapRegistererWithPrefix("resource_server_qos_", reg)
+ queue := scheduler.NewQueue(&scheduler.QueueOptions{
+ MaxSizePerTenant: cfg.QOSMaxSizePerTenant,
+ Registerer: qosReg,
+ Logger: cfg.Logger,
+ })
+ if err := services.StartAndAwaitRunning(ctx, queue); err != nil {
+ return nil, fmt.Errorf("failed to start queue: %w", err)
+ }
+ scheduler, err := scheduler.NewScheduler(queue, &scheduler.Config{
+ NumWorkers: cfg.QOSNumberWorker,
+ Logger: cfg.Logger,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to create scheduler: %w", err)
+ }
+
+ err = services.StartAndAwaitRunning(ctx, scheduler)
+ if err != nil {
+ return nil, fmt.Errorf("failed to start scheduler: %w", err)
+ }
+ serverOptions.QOSQueue = queue
+ }
+
+ server, err := sql.NewResourceServer(serverOptions)
if err != nil {
return nil, err
}
diff --git a/pkg/storage/unified/resource/errors.go b/pkg/storage/unified/resource/errors.go
index fde29c71805..903f6e4cff9 100644
--- a/pkg/storage/unified/resource/errors.go
+++ b/pkg/storage/unified/resource/errors.go
@@ -12,6 +12,7 @@ import (
grpcstatus "google.golang.org/grpc/status"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
+ "github.com/grafana/grafana/pkg/util/scheduler"
)
// Package-level errors.
@@ -50,6 +51,14 @@ func NewNotFoundError(key *resourcepb.ResourceKey) *resourcepb.ErrorResult {
}
}
+func NewTooManyRequestsError(msg string) *resourcepb.ErrorResult {
+ return &resourcepb.ErrorResult{
+ Message: msg,
+ Code: http.StatusTooManyRequests,
+ Reason: string(metav1.StatusReasonTooManyRequests),
+ }
+}
+
// Convert golang errors to status result errors that can be returned to a client
func AsErrorResult(err error) *resourcepb.ErrorResult {
if err == nil {
@@ -125,3 +134,10 @@ func GetError(res *resourcepb.ErrorResult) error {
}
return status
}
+
+func HandleQueueError[T any](err error, makeResp func(*resourcepb.ErrorResult) *T) (*T, error) {
+ if errors.Is(err, scheduler.ErrTenantQueueFull) {
+ return makeResp(NewTooManyRequestsError("tenant queue is full, please try again later")), nil
+ }
+ return makeResp(AsErrorResult(err)), nil
+}
diff --git a/pkg/storage/unified/resource/distributor.go b/pkg/storage/unified/resource/search_server_distributor.go
similarity index 95%
rename from pkg/storage/unified/resource/distributor.go
rename to pkg/storage/unified/resource/search_server_distributor.go
index fc49901d255..4fbb7083aca 100644
--- a/pkg/storage/unified/resource/distributor.go
+++ b/pkg/storage/unified/resource/search_server_distributor.go
@@ -21,7 +21,7 @@ import (
"google.golang.org/grpc/metadata"
)
-func ProvideDistributorServer(cfg *setting.Cfg, features featuremgmt.FeatureToggles, registerer prometheus.Registerer, tracer trace.Tracer, ring *ring.Ring, ringClientPool *ringclient.Pool) (grpcserver.Provider, error) {
+func ProvideSearchDistributorServer(cfg *setting.Cfg, features featuremgmt.FeatureToggles, registerer prometheus.Registerer, tracer trace.Tracer, ring *ring.Ring, ringClientPool *ringclient.Pool) (grpcserver.Provider, error) {
var err error
grpcHandler, err := grpcserver.ProvideService(cfg, features, nil, tracer, registerer)
if err != nil {
@@ -29,7 +29,7 @@ func ProvideDistributorServer(cfg *setting.Cfg, features featuremgmt.FeatureTogg
}
distributorServer := &distributorServer{
- log: log.New("unified-storage-distributor"),
+ log: log.New("index-server-distributor"),
ring: ring,
clientPool: ringClientPool,
}
@@ -73,8 +73,8 @@ func (c *RingClient) RemoteAddress() string {
return c.Conn.Target()
}
-const RingKey = "unified-storage-ring"
-const RingName = "unified_storage_ring"
+const RingKey = "search-server-ring"
+const RingName = "search_server_ring"
const RingHeartbeatTimeout = time.Minute
const RingNumTokens = 128
diff --git a/pkg/storage/unified/resource/server.go b/pkg/storage/unified/resource/server.go
index 6c07ecb401e..6e74bec40cd 100644
--- a/pkg/storage/unified/resource/server.go
+++ b/pkg/storage/unified/resource/server.go
@@ -19,9 +19,20 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
claims "github.com/grafana/authlib/types"
+ "github.com/grafana/dskit/backoff"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
+ "github.com/grafana/grafana/pkg/util/scheduler"
+)
+
+const (
+ // DefaultMaxBackoff is the default maximum backoff duration for enqueue operations.
+ DefaultMaxBackoff = 1 * time.Second
+ // DefaultMinBackoff is the default minimum backoff duration for enqueue operations.
+ DefaultMinBackoff = 100 * time.Millisecond
+ // DefaultMaxRetries is the default maximum number of retries for enqueue operations.
+ DefaultMaxRetries = 3
)
// ResourceServer implements all gRPC services
@@ -134,6 +145,10 @@ type BlobSupport interface {
// TODO? List+Delete? This is for admin access
}
+type QOSEnqueuer interface {
+ Enqueue(ctx context.Context, tenantID string, runnable func(ctx context.Context)) error
+}
+
type BlobConfig struct {
// The CDK configuration URL
URL string
@@ -203,7 +218,11 @@ type ResourceServerOptions struct {
IndexMetrics *BleveIndexMetrics
+ // MaxPageSizeBytes is the maximum size of a page in bytes.
MaxPageSizeBytes int
+
+ // QOSQueue is the quality of service queue used to enqueue
+ QOSQueue QOSEnqueuer
}
func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) {
@@ -222,6 +241,7 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) {
if opts.Diagnostics == nil {
opts.Diagnostics = &noopService{}
}
+
if opts.Now == nil {
opts.Now = func() int64 {
return time.Now().UnixMilli()
@@ -233,6 +253,10 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) {
opts.MaxPageSizeBytes = 1024 * 1024 * 2
}
+ if opts.QOSQueue == nil {
+ opts.QOSQueue = scheduler.NewNoopQueue()
+ }
+
// Initialize the blob storage
blobstore := opts.Blob.Backend
if blobstore == nil {
@@ -275,6 +299,8 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) {
storageMetrics: opts.storageMetrics,
indexMetrics: opts.IndexMetrics,
maxPageSizeBytes: opts.MaxPageSizeBytes,
+ reg: opts.Reg,
+ queue: opts.QOSQueue,
}
if opts.Search.Resources != nil {
@@ -321,6 +347,8 @@ type server struct {
initErr error
maxPageSizeBytes int
+ reg prometheus.Registerer
+ queue QOSEnqueuer
}
// Init implements ResourceServer.
@@ -570,6 +598,25 @@ func (s *server) Create(ctx context.Context, req *resourcepb.CreateRequest) (*re
return rsp, nil
}
+ var (
+ res *resourcepb.CreateResponse
+ err error
+ )
+ runErr := s.runInQueue(ctx, req.Key.Namespace, func(ctx context.Context) {
+ res, err = s.create(ctx, user, req)
+ })
+ if runErr != nil {
+ return HandleQueueError(runErr, func(e *resourcepb.ErrorResult) *resourcepb.CreateResponse {
+ return &resourcepb.CreateResponse{Error: e}
+ })
+ }
+
+ return res, err
+}
+
+func (s *server) create(ctx context.Context, user claims.AuthInfo, req *resourcepb.CreateRequest) (*resourcepb.CreateResponse, error) {
+ rsp := &resourcepb.CreateResponse{}
+
event, e := s.newEvent(ctx, user, req.Key, req.Value, nil)
if e != nil {
rsp.Error = e
@@ -605,6 +652,24 @@ func (s *server) Update(ctx context.Context, req *resourcepb.UpdateRequest) (*re
return rsp, nil
}
+ var (
+ res *resourcepb.UpdateResponse
+ err error
+ )
+ runErr := s.runInQueue(ctx, req.Key.Namespace, func(ctx context.Context) {
+ res, err = s.update(ctx, user, req)
+ })
+ if runErr != nil {
+ return HandleQueueError(runErr, func(e *resourcepb.ErrorResult) *resourcepb.UpdateResponse {
+ return &resourcepb.UpdateResponse{Error: e}
+ })
+ }
+
+ return res, err
+}
+
+func (s *server) update(ctx context.Context, user claims.AuthInfo, req *resourcepb.UpdateRequest) (*resourcepb.UpdateResponse, error) {
+ rsp := &resourcepb.UpdateResponse{}
latest := s.backend.ReadResource(ctx, &resourcepb.ReadRequest{
Key: req.Key,
})
@@ -654,6 +719,25 @@ func (s *server) Delete(ctx context.Context, req *resourcepb.DeleteRequest) (*re
return rsp, nil
}
+ var (
+ res *resourcepb.DeleteResponse
+ err error
+ )
+
+ runErr := s.runInQueue(ctx, req.Key.Namespace, func(ctx context.Context) {
+ res, err = s.delete(ctx, user, req)
+ })
+ if runErr != nil {
+ return HandleQueueError(runErr, func(e *resourcepb.ErrorResult) *resourcepb.DeleteResponse {
+ return &resourcepb.DeleteResponse{Error: e}
+ })
+ }
+
+ return res, err
+}
+
+func (s *server) delete(ctx context.Context, user claims.AuthInfo, req *resourcepb.DeleteRequest) (*resourcepb.DeleteResponse, error) {
+ rsp := &resourcepb.DeleteResponse{}
latest := s.backend.ReadResource(ctx, &resourcepb.ReadRequest{
Key: req.Key,
})
@@ -744,6 +828,23 @@ func (s *server) Read(ctx context.Context, req *resourcepb.ReadRequest) (*resour
return &resourcepb.ReadResponse{Error: NewBadRequestError("missing resource")}, nil
}
+ var (
+ res *resourcepb.ReadResponse
+ err error
+ )
+ runErr := s.runInQueue(ctx, req.Key.Namespace, func(ctx context.Context) {
+ res, err = s.read(ctx, user, req)
+ })
+ if runErr != nil {
+ return HandleQueueError(runErr, func(e *resourcepb.ErrorResult) *resourcepb.ReadResponse {
+ return &resourcepb.ReadResponse{Error: e}
+ })
+ }
+
+ return res, err
+}
+
+func (s *server) read(ctx context.Context, user claims.AuthInfo, req *resourcepb.ReadRequest) (*resourcepb.ReadResponse, error) {
rsp := s.backend.ReadResource(ctx, req)
if rsp.Error != nil && rsp.Error.Code == http.StatusNotFound {
return &resourcepb.ReadResponse{Error: rsp.Error}, nil
@@ -1237,3 +1338,41 @@ func (s *server) GetBlob(ctx context.Context, req *resourcepb.GetBlobRequest) (*
}
return rsp, nil
}
+
+func (s *server) runInQueue(ctx context.Context, tenantID string, runnable func(ctx context.Context)) error {
+ boff := backoff.New(ctx, backoff.Config{
+ MinBackoff: DefaultMinBackoff,
+ MaxBackoff: DefaultMaxBackoff,
+ MaxRetries: DefaultMaxRetries,
+ })
+
+ var (
+ wg sync.WaitGroup
+ err error
+ )
+ wg.Add(1)
+ wrapped := func(ctx context.Context) {
+ runnable(ctx)
+ wg.Done()
+ }
+ for boff.Ongoing() {
+ err = s.queue.Enqueue(ctx, tenantID, wrapped)
+ if err == nil {
+ break
+ }
+ s.log.Warn("failed to enqueue runnable, retrying",
+ "maxRetries", DefaultMaxRetries,
+ "tenantID", tenantID,
+ "error", err)
+ boff.Wait()
+ }
+ if err != nil {
+ s.log.Error("failed to enqueue runnable",
+ "maxRetries", DefaultMaxRetries,
+ "tenantID", tenantID,
+ "error", err)
+ return fmt.Errorf("failed to enqueue runnable for tenant %s: %w", tenantID, err)
+ }
+ wg.Wait()
+ return nil
+}
diff --git a/pkg/storage/unified/sql/db/dbimpl/db_engine.go b/pkg/storage/unified/sql/db/dbimpl/db_engine.go
index d170982683a..ea638c2a53f 100644
--- a/pkg/storage/unified/sql/db/dbimpl/db_engine.go
+++ b/pkg/storage/unified/sql/db/dbimpl/db_engine.go
@@ -87,9 +87,10 @@ func getEngineMySQL(getter confGetter) (*xorm.Engine, error) {
return nil, fmt.Errorf("open database: %w", err)
}
- engine.SetMaxOpenConns(0)
- engine.SetMaxIdleConns(2)
- engine.SetConnMaxLifetime(4 * time.Hour)
+ engine.SetMaxOpenConns(getter.Int("max_open_conn", 0))
+ engine.SetMaxIdleConns(getter.Int("max_idle_conn", 4))
+ maxLifetime := time.Duration(getter.Int("conn_max_lifetime", 14400)) * time.Second
+ engine.SetConnMaxLifetime(maxLifetime)
return engine, nil
}
@@ -188,5 +189,10 @@ func getEnginePostgres(getter confGetter) (*xorm.Engine, error) {
return nil, fmt.Errorf("open database: %w", err)
}
+ engine.SetMaxOpenConns(getter.Int("max_open_conn", 0))
+ engine.SetMaxIdleConns(getter.Int("max_idle_conn", 4))
+ maxLifetime := time.Duration(getter.Int("conn_max_lifetime", 14400)) * time.Second
+ engine.SetConnMaxLifetime(maxLifetime)
+
return engine, nil
}
diff --git a/pkg/storage/unified/sql/db/dbimpl/util.go b/pkg/storage/unified/sql/db/dbimpl/util.go
index da142be7d04..e2838a5a299 100644
--- a/pkg/storage/unified/sql/db/dbimpl/util.go
+++ b/pkg/storage/unified/sql/db/dbimpl/util.go
@@ -18,6 +18,7 @@ type confGetter interface {
Err() error
Bool(key string) bool
String(key string) string
+ Int(key string, def int) int
}
func newConfGetter(ds *setting.DynamicSection, keyPrefix string) confGetter {
@@ -52,6 +53,10 @@ func (g *sectionGetter) String(key string) string {
return v
}
+func (g *sectionGetter) Int(key string, def int) int {
+ return g.ds.Key(g.keyPrefix + key).MustInt(def)
+}
+
// MakeDSN creates a DSN from the given key/value pair. It validates the strings
// form valid UTF-8 sequences and escapes values if needed.
func MakeDSN(m map[string]string) (string, error) {
diff --git a/pkg/storage/unified/sql/db/dbimpl/util_test.go b/pkg/storage/unified/sql/db/dbimpl/util_test.go
index 9fff4209e3e..a46801474f3 100644
--- a/pkg/storage/unified/sql/db/dbimpl/util_test.go
+++ b/pkg/storage/unified/sql/db/dbimpl/util_test.go
@@ -28,11 +28,13 @@ func TestSectionGetter(t *testing.T) {
t.Parallel()
var (
- key = "the key"
- keyBoolTrue = "I'm true"
- keyBoolFalse = "not me!"
- prefix = "this is some prefix"
- val = string(invalidUTF8ByteSequence)
+ key = "the key"
+ keyBoolTrue = "I'm true"
+ keyBoolFalse = "not me!"
+ keyIntValid = "valid_int"
+ keyIntMissing = "missing_int"
+ prefix = "this is some prefix"
+ val = string(invalidUTF8ByteSequence)
)
t.Run("with prefix", func(t *testing.T) {
@@ -42,6 +44,8 @@ func TestSectionGetter(t *testing.T) {
prefix + key: val,
prefix + keyBoolTrue: "YES",
prefix + keyBoolFalse: "0",
+ prefix + keyIntValid: "42",
+ // Note: keyIntMissing is intentionally not included to test default behavior
}, prefix)
require.False(t, g.Bool("whatever bool"))
@@ -53,6 +57,15 @@ func TestSectionGetter(t *testing.T) {
require.True(t, g.Bool(keyBoolTrue))
require.NoError(t, g.Err())
+ require.Equal(t, 999, g.Int("whatever int", 999))
+ require.NoError(t, g.Err())
+
+ require.Equal(t, 42, g.Int(keyIntValid, 100))
+ require.NoError(t, g.Err())
+
+ require.Equal(t, 200, g.Int(keyIntMissing, 200))
+ require.NoError(t, g.Err())
+
require.Empty(t, g.String("whatever string"))
require.NoError(t, g.Err())
@@ -68,6 +81,8 @@ func TestSectionGetter(t *testing.T) {
key: val,
keyBoolTrue: "true",
keyBoolFalse: "f",
+ keyIntValid: "123",
+ // Note: keyIntMissing is intentionally not included to test default behavior
}, "")
require.False(t, g.Bool("whatever bool"))
@@ -79,6 +94,15 @@ func TestSectionGetter(t *testing.T) {
require.True(t, g.Bool(keyBoolTrue))
require.NoError(t, g.Err())
+ require.Equal(t, 500, g.Int("whatever int", 500))
+ require.NoError(t, g.Err())
+
+ require.Equal(t, 123, g.Int(keyIntValid, 0))
+ require.NoError(t, g.Err())
+
+ require.Equal(t, 300, g.Int(keyIntMissing, 300))
+ require.NoError(t, g.Err())
+
require.Empty(t, g.String("whatever string"))
require.NoError(t, g.Err())
diff --git a/pkg/storage/unified/sql/server.go b/pkg/storage/unified/sql/server.go
index 3d6b1d5f248..93f55900fd6 100644
--- a/pkg/storage/unified/sql/server.go
+++ b/pkg/storage/unified/sql/server.go
@@ -1,6 +1,7 @@
package sql
import (
+ "context"
"os"
"strings"
@@ -8,6 +9,7 @@ import (
"go.opentelemetry.io/otel/trace"
"github.com/grafana/authlib/types"
+ "github.com/grafana/dskit/services"
infraDB "github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -17,70 +19,85 @@ import (
"github.com/grafana/grafana/pkg/storage/unified/sql/db/dbimpl"
)
+type QOSEnqueueDequeuer interface {
+ services.Service
+ Enqueue(ctx context.Context, tenantID string, runnable func(ctx context.Context)) error
+ Dequeue(ctx context.Context) (func(ctx context.Context), error)
+}
+
+// ServerOptions contains the options for creating a new ResourceServer
+type ServerOptions struct {
+ DB infraDB.DB
+ Cfg *setting.Cfg
+ Tracer trace.Tracer
+ Reg prometheus.Registerer
+ AccessClient types.AccessClient
+ SearchOptions resource.SearchOptions
+ StorageMetrics *resource.StorageMetrics
+ IndexMetrics *resource.BleveIndexMetrics
+ Features featuremgmt.FeatureToggles
+ QOSQueue QOSEnqueueDequeuer
+}
+
// Creates a new ResourceServer
-func NewResourceServer(db infraDB.DB, cfg *setting.Cfg,
- tracer trace.Tracer, reg prometheus.Registerer, ac types.AccessClient,
- searchOptions resource.SearchOptions, storageMetrics *resource.StorageMetrics,
- indexMetrics *resource.BleveIndexMetrics, features featuremgmt.FeatureToggles) (resource.ResourceServer, error) {
- apiserverCfg := cfg.SectionWithEnvOverrides("grafana-apiserver")
- opts := resource.ResourceServerOptions{
- Tracer: tracer,
+func NewResourceServer(
+ opts ServerOptions,
+) (resource.ResourceServer, error) {
+ apiserverCfg := opts.Cfg.SectionWithEnvOverrides("grafana-apiserver")
+ serverOptions := resource.ResourceServerOptions{
+ Tracer: opts.Tracer,
Blob: resource.BlobConfig{
URL: apiserverCfg.Key("blob_url").MustString(""),
},
- Reg: reg,
+ Reg: opts.Reg,
}
- if ac != nil {
- opts.AccessClient = resource.NewAuthzLimitedClient(ac, resource.AuthzOptions{Tracer: tracer, Registry: reg})
+ if opts.AccessClient != nil {
+ serverOptions.AccessClient = resource.NewAuthzLimitedClient(opts.AccessClient, resource.AuthzOptions{Tracer: opts.Tracer, Registry: opts.Reg})
}
// Support local file blob
- if strings.HasPrefix(opts.Blob.URL, "./data/") {
- dir := strings.Replace(opts.Blob.URL, "./data", cfg.DataPath, 1)
+ if strings.HasPrefix(serverOptions.Blob.URL, "./data/") {
+ dir := strings.Replace(serverOptions.Blob.URL, "./data", opts.Cfg.DataPath, 1)
err := os.MkdirAll(dir, 0700)
if err != nil {
return nil, err
}
- opts.Blob.URL = "file:///" + dir
+ serverOptions.Blob.URL = "file:///" + dir
}
// This is mostly for testing, being able to influence when we paginate
// based on the page size during tests.
- unifiedStorageCfg := cfg.SectionWithEnvOverrides("unified_storage")
+ unifiedStorageCfg := opts.Cfg.SectionWithEnvOverrides("unified_storage")
maxPageSizeBytes := unifiedStorageCfg.Key("max_page_size_bytes")
- opts.MaxPageSizeBytes = maxPageSizeBytes.MustInt(0)
+ serverOptions.MaxPageSizeBytes = maxPageSizeBytes.MustInt(0)
- eDB, err := dbimpl.ProvideResourceDB(db, cfg, tracer)
+ eDB, err := dbimpl.ProvideResourceDB(opts.DB, opts.Cfg, opts.Tracer)
if err != nil {
return nil, err
}
- isHA := isHighAvailabilityEnabled(cfg.SectionWithEnvOverrides("database"),
- cfg.SectionWithEnvOverrides("resource_api"))
- withPruner := features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageHistoryPruner)
+ isHA := isHighAvailabilityEnabled(opts.Cfg.SectionWithEnvOverrides("database"),
+ opts.Cfg.SectionWithEnvOverrides("resource_api"))
+ withPruner := opts.Features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageHistoryPruner)
store, err := NewBackend(BackendOptions{
DBProvider: eDB,
- Tracer: tracer,
- Reg: reg,
+ Tracer: opts.Tracer,
+ Reg: opts.Reg,
IsHA: isHA,
withPruner: withPruner,
- storageMetrics: storageMetrics,
+ storageMetrics: opts.StorageMetrics,
})
if err != nil {
return nil, err
}
- opts.Backend = store
- opts.Diagnostics = store
- opts.Lifecycle = store
- opts.Search = searchOptions
- opts.IndexMetrics = indexMetrics
+ serverOptions.Backend = store
+ serverOptions.Diagnostics = store
+ serverOptions.Lifecycle = store
+ serverOptions.Search = opts.SearchOptions
+ serverOptions.IndexMetrics = opts.IndexMetrics
+ serverOptions.QOSQueue = opts.QOSQueue
- rs, err := resource.NewResourceServer(opts)
- if err != nil {
- return nil, err
- }
-
- return rs, nil
+ return resource.NewResourceServer(serverOptions)
}
// isHighAvailabilityEnabled determines if high availability mode should
diff --git a/pkg/storage/unified/sql/service.go b/pkg/storage/unified/sql/service.go
index 5309e23f729..c8a2ef8260d 100644
--- a/pkg/storage/unified/sql/service.go
+++ b/pkg/storage/unified/sql/service.go
@@ -34,6 +34,7 @@ import (
"github.com/grafana/grafana/pkg/storage/unified/resource/grpc"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
+ "github.com/grafana/grafana/pkg/util/scheduler"
)
var (
@@ -50,6 +51,11 @@ type UnifiedStorageGrpcService interface {
type service struct {
*services.BasicService
+ // Subservices manager
+ subservices *services.Manager
+ subservicesWatcher *services.FailureWatcher
+ hasSubservices bool
+
cfg *setting.Cfg
features featuremgmt.FeatureToggles
db infraDB.DB
@@ -71,6 +77,9 @@ type service struct {
storageRing *ring.Ring
lifecycler *ring.BasicLifecycler
+
+ queue QOSEnqueueDequeuer
+ scheduler *scheduler.Scheduler
}
func ProvideUnifiedStorageGrpcService(
@@ -85,6 +94,7 @@ func ProvideUnifiedStorageGrpcService(
storageRing *ring.Ring,
memberlistKVConfig kv.Config,
) (UnifiedStorageGrpcService, error) {
+ var err error
tracer := otel.Tracer("unified-storage")
// FIXME: This is a temporary solution while we are migrating to the new authn interceptor
@@ -95,20 +105,22 @@ func ProvideUnifiedStorageGrpcService(
})
s := &service{
- cfg: cfg,
- features: features,
- stopCh: make(chan struct{}),
- authenticator: authn,
- tracing: tracer,
- db: db,
- log: log,
- reg: reg,
- docBuilders: docBuilders,
- storageMetrics: storageMetrics,
- indexMetrics: indexMetrics,
- storageRing: storageRing,
+ cfg: cfg,
+ features: features,
+ stopCh: make(chan struct{}),
+ authenticator: authn,
+ tracing: tracer,
+ db: db,
+ log: log,
+ reg: reg,
+ docBuilders: docBuilders,
+ storageMetrics: storageMetrics,
+ indexMetrics: indexMetrics,
+ storageRing: storageRing,
+ subservicesWatcher: services.NewFailureWatcher(),
}
+ subservices := []services.Service{}
if cfg.EnableSharding {
ringStore, err := kv.NewClient(
memberlistKVConfig,
@@ -143,15 +155,50 @@ func ProvideUnifiedStorageGrpcService(
if err != nil {
return nil, fmt.Errorf("failed to initialize storage-ring lifecycler: %s", err)
}
+ subservices = append(subservices, s.lifecycler)
+ }
+
+ if cfg.QOSEnabled {
+ qosReg := prometheus.WrapRegistererWithPrefix("resource_server_qos_", reg)
+ queue := scheduler.NewQueue(&scheduler.QueueOptions{
+ MaxSizePerTenant: cfg.QOSMaxSizePerTenant,
+ Registerer: qosReg,
+ })
+ scheduler, err := scheduler.NewScheduler(queue, &scheduler.Config{
+ NumWorkers: cfg.QOSNumberWorker,
+ Logger: log,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("failed to create qos scheduler: %s", err)
+ }
+
+ s.queue = queue
+ s.scheduler = scheduler
+ subservices = append(subservices, s.queue, s.scheduler)
+ }
+
+ if len(subservices) > 0 {
+ s.hasSubservices = true
+ s.subservices, err = services.NewManager(subservices...)
+ if err != nil {
+ return nil, fmt.Errorf("failed to create subservices manager: %w", err)
+ }
}
// This will be used when running as a dskit service
- s.BasicService = services.NewBasicService(s.start, s.running, s.stopping).WithName(modules.StorageServer)
+ s.BasicService = services.NewBasicService(s.starting, s.running, s.stopping).WithName(modules.StorageServer)
return s, nil
}
-func (s *service) start(ctx context.Context) error {
+func (s *service) starting(ctx context.Context) error {
+ if s.hasSubservices {
+ s.subservicesWatcher.WatchManager(s.subservices)
+ if err := services.StartManagerAndAwaitHealthy(ctx, s.subservices); err != nil {
+ return fmt.Errorf("failed to start subservices: %w", err)
+ }
+ }
+
authzClient, err := authz.ProvideStandaloneAuthZClient(s.cfg, s.features, s.tracing)
if err != nil {
return err
@@ -162,7 +209,19 @@ func (s *service) start(ctx context.Context) error {
return err
}
- server, err := NewResourceServer(s.db, s.cfg, s.tracing, s.reg, authzClient, searchOptions, s.storageMetrics, s.indexMetrics, s.features)
+ serverOptions := ServerOptions{
+ DB: s.db,
+ Cfg: s.cfg,
+ Tracer: s.tracing,
+ Reg: s.reg,
+ AccessClient: authzClient,
+ SearchOptions: searchOptions,
+ StorageMetrics: s.storageMetrics,
+ IndexMetrics: s.indexMetrics,
+ Features: s.features,
+ QOSQueue: s.queue,
+ }
+ server, err := NewResourceServer(serverOptions)
if err != nil {
return err
}
@@ -192,11 +251,6 @@ func (s *service) start(ctx context.Context) error {
}
if s.cfg.EnableSharding {
- err = s.lifecycler.StartAsync(ctx)
- if err != nil {
- return fmt.Errorf("failed to start the lifecycler: %s", err)
- }
-
s.log.Info("waiting until resource server is JOINING in the ring")
lfcCtx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
@@ -231,15 +285,27 @@ func (s *service) GetAddress() string {
func (s *service) running(ctx context.Context) error {
select {
case err := <-s.stoppedCh:
- if err != nil {
+ if err != nil && !errors.Is(err, context.Canceled) {
return err
}
+ case err := <-s.subservicesWatcher.Chan():
+ return fmt.Errorf("subservice failure: %w", err)
case <-ctx.Done():
close(s.stopCh)
}
return nil
}
+func (s *service) stopping(_ error) error {
+ if s.hasSubservices {
+ err := services.StopManagerAndAwaitStopped(context.Background(), s.subservices)
+ if err != nil {
+ return fmt.Errorf("failed to stop subservices: %w", err)
+ }
+ }
+ return nil
+}
+
type authenticatorWithFallback struct {
authenticator func(ctx context.Context) (context.Context, error)
fallback func(ctx context.Context) (context.Context, error)
@@ -309,14 +375,6 @@ func NewAuthenticatorWithFallback(cfg *setting.Cfg, reg prometheus.Registerer, t
}
}
-func (s *service) stopping(err error) error {
- if err != nil && !errors.Is(err, context.Canceled) {
- s.log.Error("stopping unified storage grpc service", "error", err)
- return err
- }
- return nil
-}
-
func toLifecyclerConfig(cfg *setting.Cfg, logger log.Logger) (ring.BasicLifecyclerConfig, error) {
instanceAddr, err := ring.GetInstanceAddr(cfg.MemberlistBindAddr, netutil.PrivateNetworkInterfacesWithFallback([]string{"eth0", "en0"}, logger), logger, true)
if err != nil {
diff --git a/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json b/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json
index 7b2fad7ec8f..1fb2aa6d751 100644
--- a/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json
+++ b/pkg/tests/apis/openapi_snapshots/advisor.grafana.app-v0alpha1.json
@@ -1851,6 +1851,10 @@
]
}
},
+ "moreInfo": {
+ "description": "More information about the failure, not meant to be displayed to the user. Used for LLM suggestions.",
+ "type": "string"
+ },
"severity": {
"description": "Severity of the failure",
"type": "string",
diff --git a/pkg/tests/apis/secret/testdata/secure-value-default-generate.yaml b/pkg/tests/apis/secret/testdata/secure-value-default-generate.yaml
index 5bc9a368723..dec2cd8611b 100644
--- a/pkg/tests/apis/secret/testdata/secure-value-default-generate.yaml
+++ b/pkg/tests/apis/secret/testdata/secure-value-default-generate.yaml
@@ -11,5 +11,5 @@ spec:
description: This is a secret
value: this is super duper secure
decrypters:
- - actor_k6
- - actor_synthetic-monitoring
+ - k6
+ - synthetic-monitoring
diff --git a/pkg/tests/apis/secret/testdata/secure-value-generate.yaml b/pkg/tests/apis/secret/testdata/secure-value-generate.yaml
index 2743a32650b..158052350c9 100644
--- a/pkg/tests/apis/secret/testdata/secure-value-generate.yaml
+++ b/pkg/tests/apis/secret/testdata/secure-value-generate.yaml
@@ -12,5 +12,5 @@ spec:
keeper: my-keeper-1
value: super duper secure
decrypters:
- - actor_k6
- - actor_synthetic-monitoring
+ - k6
+ - synthetic-monitoring
diff --git a/pkg/tsdb/jaeger/jaeger.go b/pkg/tsdb/jaeger/jaeger.go
index a3f336c6f8d..a5c71c4327a 100644
--- a/pkg/tsdb/jaeger/jaeger.go
+++ b/pkg/tsdb/jaeger/jaeger.go
@@ -8,10 +8,9 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
-
- "github.com/grafana/grafana/pkg/infra/httpclient"
)
var logger = backend.NewLoggerWith("logger", "tsdb.jaeger")
@@ -20,7 +19,7 @@ type Service struct {
im instancemgmt.InstanceManager
}
-func ProvideService(httpClientProvider httpclient.Provider) *Service {
+func ProvideService(httpClientProvider *httpclient.Provider) *Service {
return &Service{
im: datasource.NewInstanceManager(newInstanceSettings(httpClientProvider)),
}
@@ -36,7 +35,7 @@ type datasourceJSONData struct {
} `json:"traceIdTimeParams"`
}
-func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.InstanceFactoryFunc {
+func newInstanceSettings(httpClientProvider *httpclient.Provider) datasource.InstanceFactoryFunc {
return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
httpClientOptions, err := settings.HTTPClientOptions(ctx)
if err != nil {
diff --git a/pkg/tsdb/jaeger/standalone/datasource.go b/pkg/tsdb/jaeger/standalone/datasource.go
new file mode 100644
index 00000000000..d61de484087
--- /dev/null
+++ b/pkg/tsdb/jaeger/standalone/datasource.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "context"
+
+ "github.com/grafana/grafana-plugin-sdk-go/backend"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
+
+ jaeger "github.com/grafana/grafana/pkg/tsdb/jaeger"
+)
+
+var (
+ _ backend.QueryDataHandler = (*Datasource)(nil)
+ _ backend.CheckHealthHandler = (*Datasource)(nil)
+ _ backend.CallResourceHandler = (*Datasource)(nil)
+)
+
+func NewDatasource(context.Context, backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
+ return &Datasource{
+ Service: jaeger.ProvideService(httpclient.NewProvider()),
+ }, nil
+}
+
+type Datasource struct {
+ Service *jaeger.Service
+}
+
+func (d *Datasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
+ return d.Service.QueryData(ctx, req)
+}
+
+func (d *Datasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
+ return d.Service.CallResource(ctx, req, sender)
+}
+
+func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
+ return d.Service.CheckHealth(ctx, req)
+}
diff --git a/pkg/tsdb/jaeger/standalone/main.go b/pkg/tsdb/jaeger/standalone/main.go
new file mode 100644
index 00000000000..cb20b94f200
--- /dev/null
+++ b/pkg/tsdb/jaeger/standalone/main.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+ "os"
+
+ "github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/log"
+)
+
+func main() {
+ // Start listening to requests sent from Grafana. This call is blocking so
+ // it won't finish until Grafana shuts down the process or the plugin choose
+ // to exit by itself using os.Exit. Manage automatically manages life cycle
+ // of datasource instances. It accepts datasource instance factory as first
+ // argument. This factory will be automatically called on incoming request
+ // from Grafana to create different instances of SampleDatasource (per datasource
+ // ID). When datasource configuration changed Dispose method will be called and
+ // new datasource instance created using NewSampleDatasource factory.
+ if err := datasource.Manage("jaeger", NewDatasource, datasource.ManageOpts{}); err != nil {
+ log.DefaultLogger.Error(err.Error())
+ os.Exit(1)
+ }
+}
diff --git a/pkg/tsdb/zipkin/standalone/datasource.go b/pkg/tsdb/zipkin/standalone/datasource.go
new file mode 100644
index 00000000000..25dea4d46a1
--- /dev/null
+++ b/pkg/tsdb/zipkin/standalone/datasource.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "context"
+
+ "github.com/grafana/grafana-plugin-sdk-go/backend"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
+
+ "github.com/grafana/grafana/pkg/tsdb/zipkin"
+)
+
+var (
+ _ backend.QueryDataHandler = (*Datasource)(nil)
+ _ backend.CheckHealthHandler = (*Datasource)(nil)
+ _ backend.CallResourceHandler = (*Datasource)(nil)
+)
+
+func NewDatasource(context.Context, backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
+ return &Datasource{
+ Service: zipkin.ProvideService(httpclient.NewProvider()),
+ }, nil
+}
+
+type Datasource struct {
+ Service *zipkin.Service
+}
+
+func (d *Datasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
+ return d.Service.QueryData(ctx, req)
+}
+
+func (d *Datasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
+ return d.Service.CallResource(ctx, req, sender)
+}
+
+func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
+ return d.Service.CheckHealth(ctx, req)
+}
diff --git a/pkg/tsdb/zipkin/standalone/main.go b/pkg/tsdb/zipkin/standalone/main.go
new file mode 100644
index 00000000000..0666b55e4f2
--- /dev/null
+++ b/pkg/tsdb/zipkin/standalone/main.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+ "os"
+
+ "github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/log"
+)
+
+func main() {
+ // Start listening to requests sent from Grafana. This call is blocking so
+ // it won't finish until Grafana shuts down the process or the plugin choose
+ // to exit by itself using os.Exit. Manage automatically manages life cycle
+ // of datasource instances. It accepts datasource instance factory as first
+ // argument. This factory will be automatically called on incoming request
+ // from Grafana to create different instances of SampleDatasource (per datasource
+ // ID). When datasource configuration changed Dispose method will be called and
+ // new datasource instance created using NewSampleDatasource factory.
+ if err := datasource.Manage("zipkin", NewDatasource, datasource.ManageOpts{}); err != nil {
+ log.DefaultLogger.Error(err.Error())
+ os.Exit(1)
+ }
+}
diff --git a/pkg/tsdb/zipkin/zipkin.go b/pkg/tsdb/zipkin/zipkin.go
index e6d77a82caf..0cd79b3f6fa 100644
--- a/pkg/tsdb/zipkin/zipkin.go
+++ b/pkg/tsdb/zipkin/zipkin.go
@@ -7,10 +7,9 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
+ "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
"github.com/grafana/grafana-plugin-sdk-go/backend/resource/httpadapter"
-
- "github.com/grafana/grafana/pkg/infra/httpclient"
)
var logger = backend.NewLoggerWith("logger", "tsdb.zipkin")
@@ -19,7 +18,7 @@ type Service struct {
im instancemgmt.InstanceManager
}
-func ProvideService(httpClientProvider httpclient.Provider) *Service {
+func ProvideService(httpClientProvider *httpclient.Provider) *Service {
return &Service{
im: datasource.NewInstanceManager(newInstanceSettings(httpClientProvider)),
}
@@ -29,7 +28,7 @@ type datasourceInfo struct {
ZipkinClient ZipkinClient
}
-func newInstanceSettings(httpClientProvider httpclient.Provider) datasource.InstanceFactoryFunc {
+func newInstanceSettings(httpClientProvider *httpclient.Provider) datasource.InstanceFactoryFunc {
return func(ctx context.Context, settings backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
httpClientOptions, err := settings.HTTPClientOptions(ctx)
if err != nil {
diff --git a/pkg/util/scheduler/queue.go b/pkg/util/scheduler/queue.go
index eceb0bcf38a..b065d92804f 100644
--- a/pkg/util/scheduler/queue.go
+++ b/pkg/util/scheduler/queue.go
@@ -9,6 +9,8 @@ import (
"github.com/grafana/dskit/services"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
+
+ "github.com/grafana/grafana/pkg/infra/log"
)
const (
@@ -82,6 +84,8 @@ func NewNoopQueue() *NoopQueue {
type Queue struct {
services.Service
+ logger log.Logger
+
enqueueChan chan enqueueRequest
dequeueChan chan dequeueRequest
lenChan chan lenRequest
@@ -108,6 +112,7 @@ type Queue struct {
type QueueOptions struct {
MaxSizePerTenant int
Registerer prometheus.Registerer
+ Logger log.Logger
}
// NewQueue creates a new Queue and starts its dispatcher goroutine.
@@ -116,7 +121,13 @@ func NewQueue(opts *QueueOptions) *Queue {
opts.MaxSizePerTenant = DefaultMaxSizePerTenant
}
+ if opts.Logger == nil {
+ opts.Logger = log.NewNopLogger()
+ }
+
q := &Queue{
+ logger: opts.Logger,
+
enqueueChan: make(chan enqueueRequest),
dequeueChan: make(chan dequeueRequest),
lenChan: make(chan lenRequest),
@@ -226,6 +237,8 @@ func (q *Queue) handleLenRequest(req lenRequest) {
func (q *Queue) dispatcherLoop(ctx context.Context) error {
defer close(q.dispatcherStoppedChan)
+ q.logger.Info("queue running", "maxSizePerTenant", q.maxSizePerTenant)
+
for {
q.scheduleRoundRobin()
@@ -275,7 +288,6 @@ func (q *Queue) Enqueue(ctx context.Context, tenantID string, runnable func(ctx
select {
case q.enqueueChan <- req:
err = <-respChan
- q.enqueueDuration.Observe(time.Since(start).Seconds())
case <-q.dispatcherStoppedChan:
q.discardedRequests.WithLabelValues(tenantID, "dispatcher_stopped").Inc()
err = ErrQueueClosed
@@ -283,6 +295,7 @@ func (q *Queue) Enqueue(ctx context.Context, tenantID string, runnable func(ctx
q.discardedRequests.WithLabelValues(tenantID, "context_canceled").Inc()
err = ctx.Err()
}
+ q.enqueueDuration.Observe(time.Since(start).Seconds())
return err
}
@@ -352,6 +365,8 @@ func (q *Queue) ActiveTenantsLen() int {
}
func (q *Queue) stopping(_ error) error {
+ q.logger.Info("queue stopping")
+
q.queueLength.Reset()
q.discardedRequests.Reset()
for _, tq := range q.tenantQueues {
@@ -359,5 +374,7 @@ func (q *Queue) stopping(_ error) error {
}
q.activeTenants.Init()
q.pendingDequeueRequests.Init()
+
+ q.logger.Info("queue stopped")
return nil
}
diff --git a/pkg/util/scheduler/queue_test.go b/pkg/util/scheduler/queue_test.go
index e703501747e..1f56603bf3c 100644
--- a/pkg/util/scheduler/queue_test.go
+++ b/pkg/util/scheduler/queue_test.go
@@ -11,6 +11,7 @@ import (
"time"
"github.com/grafana/dskit/services"
+ "github.com/grafana/grafana/pkg/infra/log"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
)
@@ -25,6 +26,9 @@ func QueueOptionsWithDefaults(opts *QueueOptions) *QueueOptions {
if opts.Registerer == nil {
opts.Registerer = prometheus.NewRegistry()
}
+ if opts.Logger == nil {
+ opts.Logger = log.New("qos.test")
+ }
return opts
}
diff --git a/pkg/util/scheduler/scheduler_test.go b/pkg/util/scheduler/scheduler_test.go
index f0fa02d6e4c..9d185df39c7 100644
--- a/pkg/util/scheduler/scheduler_test.go
+++ b/pkg/util/scheduler/scheduler_test.go
@@ -2,6 +2,7 @@ package scheduler
import (
"context"
+ "fmt"
"sync"
"sync/atomic"
"testing"
@@ -130,16 +131,16 @@ func TestScheduler(t *testing.T) {
t.Run("ProcessItems", func(t *testing.T) {
t.Parallel()
- q := NewQueue(QueueOptionsWithDefaults(nil))
+ q := NewQueue(QueueOptionsWithDefaults(&QueueOptions{MaxSizePerTenant: 1000}))
require.NoError(t, services.StartAndAwaitRunning(context.Background(), q))
- const itemCount = 10
+ const itemCount = 1000
var processed sync.Map
var wg sync.WaitGroup
wg.Add(itemCount)
scheduler, err := NewScheduler(q, &Config{
- NumWorkers: 2,
+ NumWorkers: 10,
MaxBackoff: 100 * time.Millisecond,
Logger: log.New("qos.test"),
})
@@ -148,8 +149,11 @@ func TestScheduler(t *testing.T) {
for i := 0; i < itemCount; i++ {
itemID := i
- require.NoError(t, q.Enqueue(context.Background(), "tenant-1", func(_ context.Context) {
+ tenantIndex := itemID % 10
+ tenantID := fmt.Sprintf("tenant-%d", tenantIndex)
+ require.NoError(t, q.Enqueue(context.Background(), tenantID, func(_ context.Context) {
processed.Store(itemID, true)
+ time.Sleep(10 * time.Millisecond)
wg.Done()
}))
}
diff --git a/public/app/api/clients/advisor/v0alpha1/endpoints.gen.ts b/public/app/api/clients/advisor/v0alpha1/endpoints.gen.ts
index afadfbb5bf3..86ca7fa2a38 100644
--- a/public/app/api/clients/advisor/v0alpha1/endpoints.gen.ts
+++ b/public/app/api/clients/advisor/v0alpha1/endpoints.gen.ts
@@ -1,11 +1,15 @@
import { api } from './baseAPI';
-export const addTagTypes = ['Check', 'CheckType'] as const;
+export const addTagTypes = ['API Discovery', 'Check', 'CheckType'] as const;
const injectedRtkApi = api
.enhanceEndpoints({
addTagTypes,
})
.injectEndpoints({
endpoints: (build) => ({
+ getApiResources: build.query({
+ query: () => ({ url: `/apis/advisor.grafana.app/v0alpha1/` }),
+ providesTags: ['API Discovery'],
+ }),
listCheck: build.query({
query: (queryArg) => ({
url: `/checks`,
@@ -39,6 +43,29 @@ const injectedRtkApi = api
}),
invalidatesTags: ['Check'],
}),
+ deletecollectionCheck: build.mutation({
+ query: (queryArg) => ({
+ url: `/checks`,
+ method: 'DELETE',
+ params: {
+ pretty: queryArg.pretty,
+ continue: queryArg['continue'],
+ dryRun: queryArg.dryRun,
+ fieldSelector: queryArg.fieldSelector,
+ gracePeriodSeconds: queryArg.gracePeriodSeconds,
+ ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential,
+ labelSelector: queryArg.labelSelector,
+ limit: queryArg.limit,
+ orphanDependents: queryArg.orphanDependents,
+ propagationPolicy: queryArg.propagationPolicy,
+ resourceVersion: queryArg.resourceVersion,
+ resourceVersionMatch: queryArg.resourceVersionMatch,
+ sendInitialEvents: queryArg.sendInitialEvents,
+ timeoutSeconds: queryArg.timeoutSeconds,
+ },
+ }),
+ invalidatesTags: ['Check'],
+ }),
getCheck: build.query({
query: (queryArg) => ({
url: `/checks/${queryArg.name}`,
@@ -48,6 +75,20 @@ const injectedRtkApi = api
}),
providesTags: ['Check'],
}),
+ replaceCheck: build.mutation({
+ query: (queryArg) => ({
+ url: `/checks/${queryArg.name}`,
+ method: 'PUT',
+ body: queryArg.check,
+ params: {
+ pretty: queryArg.pretty,
+ dryRun: queryArg.dryRun,
+ fieldManager: queryArg.fieldManager,
+ fieldValidation: queryArg.fieldValidation,
+ },
+ }),
+ invalidatesTags: ['Check'],
+ }),
deleteCheck: build.mutation({
query: (queryArg) => ({
url: `/checks/${queryArg.name}`,
@@ -97,6 +138,81 @@ const injectedRtkApi = api
}),
providesTags: ['CheckType'],
}),
+ createCheckType: build.mutation({
+ query: (queryArg) => ({
+ url: `/checktypes`,
+ method: 'POST',
+ body: queryArg.checkType,
+ params: {
+ pretty: queryArg.pretty,
+ dryRun: queryArg.dryRun,
+ fieldManager: queryArg.fieldManager,
+ fieldValidation: queryArg.fieldValidation,
+ },
+ }),
+ invalidatesTags: ['CheckType'],
+ }),
+ deletecollectionCheckType: build.mutation({
+ query: (queryArg) => ({
+ url: `/checktypes`,
+ method: 'DELETE',
+ params: {
+ pretty: queryArg.pretty,
+ continue: queryArg['continue'],
+ dryRun: queryArg.dryRun,
+ fieldSelector: queryArg.fieldSelector,
+ gracePeriodSeconds: queryArg.gracePeriodSeconds,
+ ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential,
+ labelSelector: queryArg.labelSelector,
+ limit: queryArg.limit,
+ orphanDependents: queryArg.orphanDependents,
+ propagationPolicy: queryArg.propagationPolicy,
+ resourceVersion: queryArg.resourceVersion,
+ resourceVersionMatch: queryArg.resourceVersionMatch,
+ sendInitialEvents: queryArg.sendInitialEvents,
+ timeoutSeconds: queryArg.timeoutSeconds,
+ },
+ }),
+ invalidatesTags: ['CheckType'],
+ }),
+ getCheckType: build.query({
+ query: (queryArg) => ({
+ url: `/checktypes/${queryArg.name}`,
+ params: {
+ pretty: queryArg.pretty,
+ },
+ }),
+ providesTags: ['CheckType'],
+ }),
+ replaceCheckType: build.mutation({
+ query: (queryArg) => ({
+ url: `/checktypes/${queryArg.name}`,
+ method: 'PUT',
+ body: queryArg.checkType,
+ params: {
+ pretty: queryArg.pretty,
+ dryRun: queryArg.dryRun,
+ fieldManager: queryArg.fieldManager,
+ fieldValidation: queryArg.fieldValidation,
+ },
+ }),
+ invalidatesTags: ['CheckType'],
+ }),
+ deleteCheckType: build.mutation({
+ query: (queryArg) => ({
+ url: `/checktypes/${queryArg.name}`,
+ method: 'DELETE',
+ params: {
+ pretty: queryArg.pretty,
+ dryRun: queryArg.dryRun,
+ gracePeriodSeconds: queryArg.gracePeriodSeconds,
+ ignoreStoreReadErrorWithClusterBreakingPotential: queryArg.ignoreStoreReadErrorWithClusterBreakingPotential,
+ orphanDependents: queryArg.orphanDependents,
+ propagationPolicy: queryArg.propagationPolicy,
+ },
+ }),
+ invalidatesTags: ['CheckType'],
+ }),
updateCheckType: build.mutation({
query: (queryArg) => ({
url: `/checktypes/${queryArg.name}`,
@@ -116,6 +232,8 @@ const injectedRtkApi = api
overrideExisting: false,
});
export { injectedRtkApi as generatedAPI };
+export type GetApiResourcesApiResponse = /** status 200 OK */ ApiResourceList;
+export type GetApiResourcesApiArg = void;
export type ListCheckApiResponse = /** status 200 OK */ CheckList;
export type ListCheckApiArg = {
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
@@ -176,6 +294,57 @@ export type CreateCheckApiArg = {
fieldValidation?: string;
check: Check;
};
+export type DeletecollectionCheckApiResponse = /** status 200 OK */ Status;
+export type DeletecollectionCheckApiArg = {
+ /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
+ pretty?: string;
+ /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key".
+
+ This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */
+ continue?: string;
+ /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
+ dryRun?: string;
+ /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */
+ fieldSelector?: string;
+ /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */
+ gracePeriodSeconds?: number;
+ /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */
+ ignoreStoreReadErrorWithClusterBreakingPotential?: boolean;
+ /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */
+ labelSelector?: string;
+ /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.
+
+ The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */
+ limit?: number;
+ /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */
+ orphanDependents?: boolean;
+ /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */
+ propagationPolicy?: string;
+ /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.
+
+ Defaults to unset */
+ resourceVersion?: string;
+ /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.
+
+ Defaults to unset */
+ resourceVersionMatch?: string;
+ /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.
+
+ When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan
+ is interpreted as "data at least as new as the provided `resourceVersion`"
+ and the bookmark event is send when the state is synced
+ to a `resourceVersion` at least as fresh as the one provided by the ListOptions.
+ If `resourceVersion` is unset, this is interpreted as "consistent read" and the
+ bookmark event is send when the state is synced at least to the moment
+ when request started being processed.
+ - `resourceVersionMatch` set to any other value or unset
+ Invalid error is returned.
+
+ Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */
+ sendInitialEvents?: boolean;
+ /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */
+ timeoutSeconds?: number;
+};
export type GetCheckApiResponse = /** status 200 OK */ Check;
export type GetCheckApiArg = {
/** name of the Check */
@@ -183,6 +352,20 @@ export type GetCheckApiArg = {
/** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
pretty?: string;
};
+export type ReplaceCheckApiResponse = /** status 200 OK */ Check | /** status 201 Created */ Check;
+export type ReplaceCheckApiArg = {
+ /** name of the Check */
+ name: string;
+ /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
+ pretty?: string;
+ /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
+ dryRun?: string;
+ /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */
+ fieldManager?: string;
+ /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */
+ fieldValidation?: string;
+ check: Check;
+};
export type DeleteCheckApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status;
export type DeleteCheckApiArg = {
/** name of the Check */
@@ -261,6 +444,110 @@ export type ListCheckTypeApiArg = {
/** Watch for changes to the described resources and return them as a stream of add, update, and remove notifications. Specify resourceVersion. */
watch?: boolean;
};
+export type CreateCheckTypeApiResponse = /** status 200 OK */
+ | CheckType
+ | /** status 201 Created */ CheckType
+ | /** status 202 Accepted */ CheckType;
+export type CreateCheckTypeApiArg = {
+ /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
+ pretty?: string;
+ /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
+ dryRun?: string;
+ /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */
+ fieldManager?: string;
+ /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */
+ fieldValidation?: string;
+ checkType: CheckType;
+};
+export type DeletecollectionCheckTypeApiResponse = /** status 200 OK */ Status;
+export type DeletecollectionCheckTypeApiArg = {
+ /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
+ pretty?: string;
+ /** The continue option should be set when retrieving more results from the server. Since this value is server defined, clients may only use the continue value from a previous query result with identical query parameters (except for the value of continue) and the server may reject a continue value it does not recognize. If the specified continue value is no longer valid whether due to expiration (generally five to fifteen minutes) or a configuration change on the server, the server will respond with a 410 ResourceExpired error together with a continue token. If the client needs a consistent list, it must restart their list without the continue field. Otherwise, the client may send another list request with the token received with the 410 error, the server will respond with a list starting from the next key, but from the latest snapshot, which is inconsistent from the previous list results - objects that are created, modified, or deleted after the first list request will be included in the response, as long as their keys are after the "next key".
+
+ This field is not supported when watch is true. Clients may start a watch from the last resourceVersion value returned by the server and not miss any modifications. */
+ continue?: string;
+ /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
+ dryRun?: string;
+ /** A selector to restrict the list of returned objects by their fields. Defaults to everything. */
+ fieldSelector?: string;
+ /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */
+ gracePeriodSeconds?: number;
+ /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */
+ ignoreStoreReadErrorWithClusterBreakingPotential?: boolean;
+ /** A selector to restrict the list of returned objects by their labels. Defaults to everything. */
+ labelSelector?: string;
+ /** limit is a maximum number of responses to return for a list call. If more items exist, the server will set the `continue` field on the list metadata to a value that can be used with the same initial query to retrieve the next set of results. Setting a limit may return fewer than the requested amount of items (up to zero items) in the event all requested objects are filtered out and clients should only use the presence of the continue field to determine whether more results are available. Servers may choose not to support the limit argument and will return all of the available results. If limit is specified and the continue field is empty, clients may assume that no more results are available. This field is not supported if watch is true.
+
+ The server guarantees that the objects returned when using continue will be identical to issuing a single list call without a limit - that is, no objects created, modified, or deleted after the first request is issued will be included in any subsequent continued requests. This is sometimes referred to as a consistent snapshot, and ensures that a client that is using limit to receive smaller chunks of a very large result can ensure they see all possible objects. If objects are updated during a chunked list the version of the object that was present at the time the first list result was calculated is returned. */
+ limit?: number;
+ /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */
+ orphanDependents?: boolean;
+ /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */
+ propagationPolicy?: string;
+ /** resourceVersion sets a constraint on what resource versions a request may be served from. See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.
+
+ Defaults to unset */
+ resourceVersion?: string;
+ /** resourceVersionMatch determines how resourceVersion is applied to list calls. It is highly recommended that resourceVersionMatch be set for list calls where resourceVersion is set See https://kubernetes.io/docs/reference/using-api/api-concepts/#resource-versions for details.
+
+ Defaults to unset */
+ resourceVersionMatch?: string;
+ /** `sendInitialEvents=true` may be set together with `watch=true`. In that case, the watch stream will begin with synthetic events to produce the current state of objects in the collection. Once all such events have been sent, a synthetic "Bookmark" event will be sent. The bookmark will report the ResourceVersion (RV) corresponding to the set of objects, and be marked with `"k8s.io/initial-events-end": "true"` annotation. Afterwards, the watch stream will proceed as usual, sending watch events corresponding to changes (subsequent to the RV) to objects watched.
+
+ When `sendInitialEvents` option is set, we require `resourceVersionMatch` option to also be set. The semantic of the watch request is as following: - `resourceVersionMatch` = NotOlderThan
+ is interpreted as "data at least as new as the provided `resourceVersion`"
+ and the bookmark event is send when the state is synced
+ to a `resourceVersion` at least as fresh as the one provided by the ListOptions.
+ If `resourceVersion` is unset, this is interpreted as "consistent read" and the
+ bookmark event is send when the state is synced at least to the moment
+ when request started being processed.
+ - `resourceVersionMatch` set to any other value or unset
+ Invalid error is returned.
+
+ Defaults to true if `resourceVersion=""` or `resourceVersion="0"` (for backward compatibility reasons) and to false otherwise. */
+ sendInitialEvents?: boolean;
+ /** Timeout for the list/watch call. This limits the duration of the call, regardless of any activity or inactivity. */
+ timeoutSeconds?: number;
+};
+export type GetCheckTypeApiResponse = /** status 200 OK */ CheckType;
+export type GetCheckTypeApiArg = {
+ /** name of the CheckType */
+ name: string;
+ /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
+ pretty?: string;
+};
+export type ReplaceCheckTypeApiResponse = /** status 200 OK */ CheckType | /** status 201 Created */ CheckType;
+export type ReplaceCheckTypeApiArg = {
+ /** name of the CheckType */
+ name: string;
+ /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
+ pretty?: string;
+ /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
+ dryRun?: string;
+ /** fieldManager is a name associated with the actor or entity that is making these changes. The value must be less than or 128 characters long, and only contain printable characters, as defined by https://golang.org/pkg/unicode/#IsPrint. */
+ fieldManager?: string;
+ /** fieldValidation instructs the server on how to handle objects in the request (POST/PUT/PATCH) containing unknown or duplicate fields. Valid values are: - Ignore: This will ignore any unknown fields that are silently dropped from the object, and will ignore all but the last duplicate field that the decoder encounters. This is the default behavior prior to v1.23. - Warn: This will send a warning via the standard warning response header for each unknown field that is dropped from the object, and for each duplicate field that is encountered. The request will still succeed if there are no other errors, and will only persist the last of any duplicate fields. This is the default in v1.23+ - Strict: This will fail the request with a BadRequest error if any unknown fields would be dropped from the object, or if any duplicate fields are present. The error returned from the server will contain all unknown and duplicate fields encountered. */
+ fieldValidation?: string;
+ checkType: CheckType;
+};
+export type DeleteCheckTypeApiResponse = /** status 200 OK */ Status | /** status 202 Accepted */ Status;
+export type DeleteCheckTypeApiArg = {
+ /** name of the CheckType */
+ name: string;
+ /** If 'true', then the output is pretty printed. Defaults to 'false' unless the user-agent indicates a browser or command-line HTTP tool (curl and wget). */
+ pretty?: string;
+ /** When present, indicates that modifications should not be persisted. An invalid or unrecognized dryRun directive will result in an error response and no further processing of the request. Valid values are: - All: all dry run stages will be processed */
+ dryRun?: string;
+ /** The duration in seconds before the object should be deleted. Value must be non-negative integer. The value zero indicates delete immediately. If this value is nil, the default grace period for the specified type will be used. Defaults to a per object value if not specified. zero means delete immediately. */
+ gracePeriodSeconds?: number;
+ /** if set to true, it will trigger an unsafe deletion of the resource in case the normal deletion flow fails with a corrupt object error. A resource is considered corrupt if it can not be retrieved from the underlying storage successfully because of a) its data can not be transformed e.g. decryption failure, or b) it fails to decode into an object. NOTE: unsafe deletion ignores finalizer constraints, skips precondition checks, and removes the object from the storage. WARNING: This may potentially break the cluster if the workload associated with the resource being unsafe-deleted relies on normal deletion flow. Use only if you REALLY know what you are doing. The default value is false, and the user must opt in to enable it */
+ ignoreStoreReadErrorWithClusterBreakingPotential?: boolean;
+ /** Deprecated: please use the PropagationPolicy, this field will be deprecated in 1.7. Should the dependent objects be orphaned. If true/false, the "orphan" finalizer will be added to/removed from the object's finalizers list. Either this field or PropagationPolicy may be set, but not both. */
+ orphanDependents?: boolean;
+ /** Whether and how garbage collection will be performed. Either this field or OrphanDependents may be set, but not both. The default policy is decided by the existing finalizer set in the metadata.finalizers and the resource-specific default policy. Acceptable values are: 'Orphan' - orphan the dependents; 'Background' - allow the garbage collector to delete the dependents in the background; 'Foreground' - a cascading policy that deletes all dependents in the foreground. */
+ propagationPolicy?: string;
+};
export type UpdateCheckTypeApiResponse = /** status 200 OK */ CheckType | /** status 201 Created */ CheckType;
export type UpdateCheckTypeApiArg = {
/** name of the CheckType */
@@ -277,6 +564,38 @@ export type UpdateCheckTypeApiArg = {
force?: boolean;
patch: Patch;
};
+export type ApiResource = {
+ /** categories is a list of the grouped resources this resource belongs to (e.g. 'all') */
+ categories?: string[];
+ /** group is the preferred group of the resource. Empty implies the group of the containing resource list. For subresources, this may have a different value, for example: Scale". */
+ group?: string;
+ /** kind is the kind for the resource (e.g. 'Foo' is the kind for a resource 'foo') */
+ kind: string;
+ /** name is the plural name of the resource. */
+ name: string;
+ /** namespaced indicates if a resource is namespaced or not. */
+ namespaced: boolean;
+ /** shortNames is a list of suggested short names of the resource. */
+ shortNames?: string[];
+ /** singularName is the singular name of the resource. This allows clients to handle plural and singular opaquely. The singularName is more correct for reporting status on a single item and both singular and plural are allowed from the kubectl CLI interface. */
+ singularName: string;
+ /** The hash value of the storage version, the version this resource is converted to when written to the data store. Value must be treated as opaque by clients. Only equality comparison on the value is valid. This is an alpha feature and may change or be removed in the future. The field is populated by the apiserver only if the StorageVersionHash feature gate is enabled. This field will remain optional even if it graduates. */
+ storageVersionHash?: string;
+ /** verbs is a list of supported kube verbs (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy) */
+ verbs: string[];
+ /** version is the preferred version of the resource. Empty implies the version of the containing resource list For subresources, this may have a different value, for example: v1 (while inside a v1beta1 version of the core resource's group)". */
+ version?: string;
+};
+export type ApiResourceList = {
+ /** 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 */
+ apiVersion?: string;
+ /** groupVersion is the group and version this APIResourceList is for. */
+ groupVersion: string;
+ /** 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 */
+ kind?: string;
+ /** resources contains the name of the resources and if they are namespaced. */
+ resources: ApiResource[];
+};
export type Time = string;
export type FieldsV1 = object;
export type ManagedFieldsEntry = {
@@ -390,6 +709,8 @@ export type CheckReportFailure = {
itemID: string;
/** Links to actions that can be taken to resolve the failure */
links: CheckErrorLink[];
+ /** More information about the failure */
+ moreInfo?: string;
/** Severity of the failure */
severity: string;
/** Step ID that the failure is associated with */
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
index d5ff767a76f..f88931de9b6 100644
--- 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
@@ -4,6 +4,7 @@ exports[`Moving a Data source managed rule should move a rule in a namespace to
[
{
"body": {
+ "interval": "1m",
"name": "group-1",
"rules": [
{
@@ -49,6 +50,7 @@ exports[`Moving a Data source managed rule should move a rule in an existing gro
[
{
"body": {
+ "interval": "1m",
"name": "entirely new group name",
"rules": [
{
@@ -190,6 +192,7 @@ exports[`Moving a Grafana managed rule should move a rule from an existing group
[
{
"body": {
+ "interval": "1m",
"name": "empty-group",
"rules": [
{
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
index 1b0143c619e..6afe36981f9 100644
--- 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
@@ -4,6 +4,7 @@ exports[`Updating a Data source managed rule should be able to move a rule if ta
[
{
"body": {
+ "interval": "1m",
"name": "a new group",
"rules": [
{
@@ -144,7 +145,7 @@ exports[`Updating a Grafana managed rule should move a rule in to another group
[
{
"body": {
- "interval": "1m",
+ "interval": "5m",
"name": "grafana-group-2",
"rules": [
{
diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/useProduceNewRuleGroup.ts b/public/app/features/alerting/unified/hooks/ruleGroup/useProduceNewRuleGroup.ts
index ddbbc0e2401..c5e0eb9faf7 100644
--- a/public/app/features/alerting/unified/hooks/ruleGroup/useProduceNewRuleGroup.ts
+++ b/public/app/features/alerting/unified/hooks/ruleGroup/useProduceNewRuleGroup.ts
@@ -6,7 +6,7 @@ import { PostableRulerRuleGroupDTO } from 'app/types/unified-alerting-dto';
import { alertRuleApi } from '../../api/alertRuleApi';
import { featureDiscoveryApi } from '../../api/featureDiscoveryApi';
import { notFoundToNullOrThrow } from '../../api/util';
-import { ruleGroupReducer } from '../../reducers/ruler/ruleGroups';
+import { addRuleAction, ruleGroupReducer } from '../../reducers/ruler/ruleGroups';
import { DEFAULT_GROUP_EVALUATION_INTERVAL } from '../../rule-editor/formDefaults';
import { getDatasourceAPIUid } from '../../utils/datasource';
@@ -62,10 +62,15 @@ export function useProduceNewRuleGroup() {
.catch(notFoundToNullOrThrow);
const initialRuleGroupDefinition = latestRuleGroupDefinition ?? createBlankRuleGroup(groupName);
- const newRuleGroupDefinition = actions.reduce(
- (ruleGroup, action) => ruleGroupReducer(ruleGroup, action),
- initialRuleGroupDefinition
- );
+ const newRuleGroupDefinition = actions.reduce((ruleGroup, action) => {
+ // This is a workaround to ensure that the interval is set correctly when adding a rule to an existing rule group.
+ // The interval is set to default for DMA rules even for existing rule groups with a non-default interval.
+ // We no longer allow setting the interval for existing groups, but still allow that when you create a new rule group.
+ if (latestRuleGroupDefinition && addRuleAction.match(action)) {
+ action.payload.interval = latestRuleGroupDefinition.interval;
+ }
+ return ruleGroupReducer(ruleGroup, action);
+ }, initialRuleGroupDefinition);
return { newRuleGroupDefinition, rulerConfig };
};
diff --git a/public/app/features/alerting/unified/hooks/ruleGroup/useUpdateRuleInRuleGroup.test.tsx b/public/app/features/alerting/unified/hooks/ruleGroup/useUpdateRuleInRuleGroup.test.tsx
index 2324ed363db..739e57a86f6 100644
--- a/public/app/features/alerting/unified/hooks/ruleGroup/useUpdateRuleInRuleGroup.test.tsx
+++ b/public/app/features/alerting/unified/hooks/ruleGroup/useUpdateRuleInRuleGroup.test.tsx
@@ -9,8 +9,8 @@ import { PostableRuleDTO } from 'app/types/unified-alerting-dto';
import { setupMswServer } from '../../mockApi';
import { grantUserPermissions } from '../../mocks';
import {
- grafanaRulerGroupName,
- grafanaRulerGroupName2,
+ grafanaRulerGroup,
+ grafanaRulerGroup2,
grafanaRulerNamespace,
grafanaRulerRule,
} from '../../mocks/grafanaRulerApi';
@@ -41,7 +41,7 @@ describe('Updating a Grafana managed rule', () => {
const ruleGroupID: RuleGroupIdentifier = {
dataSourceName: GRAFANA_RULES_SOURCE_NAME,
- groupName: grafanaRulerGroupName,
+ groupName: grafanaRulerGroup.name,
namespaceName: grafanaRulerNamespace.uid,
};
@@ -71,13 +71,13 @@ describe('Updating a Grafana managed rule', () => {
const ruleGroupID: RuleGroupIdentifier = {
dataSourceName: GRAFANA_RULES_SOURCE_NAME,
- groupName: grafanaRulerGroupName,
+ groupName: grafanaRulerGroup.name,
namespaceName: grafanaRulerNamespace.uid,
};
const targetRuleGroupID: RuleGroupIdentifier = {
dataSourceName: GRAFANA_RULES_SOURCE_NAME,
- groupName: grafanaRulerGroupName2,
+ groupName: grafanaRulerGroup2.name,
namespaceName: grafanaRulerNamespace.uid,
};
@@ -110,7 +110,7 @@ describe('Updating a Grafana managed rule', () => {
it('should fail if the rule does not exist in the group', async () => {
const ruleGroupID: RuleGroupIdentifier = {
dataSourceName: GRAFANA_RULES_SOURCE_NAME,
- groupName: grafanaRulerGroupName,
+ groupName: grafanaRulerGroup.name,
namespaceName: grafanaRulerNamespace.uid,
};
diff --git a/public/app/features/alerting/unified/mocks/grafanaRulerApi.ts b/public/app/features/alerting/unified/mocks/grafanaRulerApi.ts
index 13eb4185823..18c690e7678 100644
--- a/public/app/features/alerting/unified/mocks/grafanaRulerApi.ts
+++ b/public/app/features/alerting/unified/mocks/grafanaRulerApi.ts
@@ -70,7 +70,7 @@ export const grafanaRulerGroup: RulerRuleGroupDTO = {
export const grafanaRulerGroup2: RulerRuleGroupDTO = {
name: grafanaRulerGroupName2,
- interval: '1m',
+ interval: '5m',
rules: [grafanaRulerRule],
};
diff --git a/public/app/features/alerting/unified/mocks/server/handlers/grafanaRuler.ts b/public/app/features/alerting/unified/mocks/server/handlers/grafanaRuler.ts
index 6177a07618d..43dd6761400 100644
--- a/public/app/features/alerting/unified/mocks/server/handlers/grafanaRuler.ts
+++ b/public/app/features/alerting/unified/mocks/server/handlers/grafanaRuler.ts
@@ -71,15 +71,17 @@ export const rulerRuleGroupHandler = (options?: HandlerOptions) => {
return options.response;
}
- // This mimic API response as closely as possible.
- // Invalid folderUid returns 403 but invalid group will return 202 with empty list of rules
- // This should be fixed soon to return 404 instead of 202
const namespace = rulerTestDb.getNamespace(folderUid);
if (!namespace) {
return new HttpResponse(null, { status: 403 });
}
const matchingGroup = rulerTestDb.getGroup(folderUid, groupName);
+
+ if (!matchingGroup) {
+ return new HttpResponse({ message: 'group does not exist' }, { status: 404 });
+ }
+
return HttpResponse.json({
name: groupName,
interval: matchingGroup?.interval,
diff --git a/public/app/features/alerting/unified/mocks/server/handlers/mimirRuler.ts b/public/app/features/alerting/unified/mocks/server/handlers/mimirRuler.ts
index 6dbdff31031..a0bee5efaf3 100644
--- a/public/app/features/alerting/unified/mocks/server/handlers/mimirRuler.ts
+++ b/public/app/features/alerting/unified/mocks/server/handlers/mimirRuler.ts
@@ -53,6 +53,11 @@ export const rulerRuleGroupHandler = (options?: HandlerOptions) => {
}
const matchingGroup = namespace.find((group) => group.name === groupName);
+
+ if (!matchingGroup) {
+ return HttpResponse.json({ message: 'group does not exist' }, { status: 404 });
+ }
+
return HttpResponse.json({
name: groupName,
interval: matchingGroup?.interval,
diff --git a/public/app/features/alerting/unified/reducers/ruler/ruleGroups.ts b/public/app/features/alerting/unified/reducers/ruler/ruleGroups.ts
index b8d91042b2c..82224e02d65 100644
--- a/public/app/features/alerting/unified/reducers/ruler/ruleGroups.ts
+++ b/public/app/features/alerting/unified/reducers/ruler/ruleGroups.ts
@@ -9,6 +9,8 @@ import { hashRulerRule } from '../../utils/rule-id';
import { isCloudRuleIdentifier, isGrafanaRuleIdentifier, rulerRuleType } from '../../utils/rules';
// rule-scoped actions
+// TOOD The interval field only make sense when adding a rule to a new rule group.
+// We need to split these into distinct actions and introduce a separete addNewRuleGroupAction.
export const addRuleAction = createAction<{ rule: PostableRuleDTO; groupName?: string; interval?: string }>(
'ruleGroup/rules/add'
);
diff --git a/public/app/features/alerting/unified/rule-editor/RuleEditorCloudRules.test.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditorCloudRules.test.tsx
index 185a6b69d32..ac0668721cb 100644
--- a/public/app/features/alerting/unified/rule-editor/RuleEditorCloudRules.test.tsx
+++ b/public/app/features/alerting/unified/rule-editor/RuleEditorCloudRules.test.tsx
@@ -8,7 +8,7 @@ import { AccessControlAction } from 'app/types';
import { ExpressionEditorProps } from '../components/rule-editor/ExpressionEditor';
import { setupMswServer } from '../mockApi';
import { grantUserPermissions } from '../mocks';
-import { GROUP_3, NAMESPACE_2 } from '../mocks/mimirRulerApi';
+import { GROUP_3, GROUP_4, 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';
@@ -86,4 +86,52 @@ describe('RuleEditor cloud', () => {
const serializedRequests = await serializeRequests(requests);
expect(serializedRequests).toMatchSnapshot();
});
+
+ it('should keep existing rule interval duration when attaching new rules', async () => {
+ const { user } = renderRuleEditor();
+
+ const removeExpressionsButtons = await screen.findAllByLabelText(/Remove expression/);
+ expect(removeExpressionsButtons).toHaveLength(2);
+
+ // Needs to wait for feature discovery API call to finish - Check if ruler enabled
+ expect(await screen.findByText('Data source-managed')).toBeInTheDocument();
+
+ const switchToCloudButton = screen.getByText('Data source-managed');
+ expect(switchToCloudButton).toBeInTheDocument();
+ expect(switchToCloudButton).toBeEnabled();
+
+ await user.click(switchToCloudButton);
+
+ //expressions are removed after switching to data-source managed
+ expect(screen.queryAllByLabelText(/Remove expression/)).toHaveLength(0);
+
+ expect(screen.getByTestId(selectors.components.DataSourcePicker.inputV2)).toBeInTheDocument();
+
+ const dataSourceSelect = await ui.inputs.dataSource.find();
+ await user.click(dataSourceSelect);
+ 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 with 3m interval');
+ await clickSelectOption(ui.inputs.namespace.get(), NAMESPACE_2);
+ await clickSelectOption(ui.inputs.group.get(), GROUP_4);
+
+ await user.type(ui.inputs.annotationValue(0).get(), 'some summary');
+ await user.type(ui.inputs.annotationValue(1).get(), 'some description');
+
+ // TODO remove skipPointerEventsCheck once https://github.com/jsdom/jsdom/issues/3232 is fixed
+ await user.click(ui.buttons.addLabel.get());
+
+ // save and check what was sent to backend
+ const capture = captureRequests();
+ await user.click(ui.buttons.save.get());
+ const requests = await capture;
+
+ const serializedRequests = await serializeRequests(requests);
+ const saveRequest = serializedRequests.find((req) => req.method === 'POST');
+
+ expect(saveRequest).toBeDefined();
+ expect(saveRequest?.body).toMatchObject({ interval: '3m' });
+ });
});
diff --git a/public/app/features/alerting/unified/rule-editor/RuleEditorGrafanaRules.test.tsx b/public/app/features/alerting/unified/rule-editor/RuleEditorGrafanaRules.test.tsx
index f6267271e56..fedb898f955 100644
--- a/public/app/features/alerting/unified/rule-editor/RuleEditorGrafanaRules.test.tsx
+++ b/public/app/features/alerting/unified/rule-editor/RuleEditorGrafanaRules.test.tsx
@@ -11,7 +11,7 @@ import { DashboardSearchItemType } from 'app/features/search/types';
import { AccessControlAction } from 'app/types';
import { grantUserPermissions, mockDataSource, mockFolder } from '../mocks';
-import { grafanaRulerGroup, grafanaRulerRule } from '../mocks/grafanaRulerApi';
+import { grafanaRulerGroup, grafanaRulerGroup2, grafanaRulerRule } from '../mocks/grafanaRulerApi';
import { setFolderResponse } from '../mocks/server/configure';
import { captureRequests, serializeRequests } from '../mocks/server/events';
import { setupDataSources } from '../testSetup/datasources';
@@ -140,4 +140,47 @@ describe('RuleEditor grafana managed rules', () => {
const serializedRequests = await serializeRequests(requests);
expect(serializedRequests).toMatchSnapshot();
});
+
+ it('should keep existing group interval when creating new rule in existing group', async () => {
+ const capture = captureRequests((r) => r.method === 'POST' && r.url.includes('/api/ruler/'));
+
+ const { user } = renderRuleEditor();
+
+ await user.type(await ui.inputs.name.find(), 'my great new rule');
+ await user.click(await screen.findByRole('button', { name: /select folder/i }));
+ await user.click(await screen.findByLabelText(/folder a/i));
+
+ // Select the existing group with 5m interval
+ const groupInput = await ui.inputs.group.find();
+ await user.click(await byRole('combobox').find(groupInput));
+ await clickSelectOption(groupInput, grafanaRulerGroup2.name);
+ await user.type(ui.inputs.annotationValue(1).get(), 'some description');
+
+ // Set pending period to none (0s) to avoid validation errors
+ const pendingPeriodInput = await ui.inputs.pendingPeriod.find();
+ await user.clear(pendingPeriodInput);
+ await user.type(pendingPeriodInput, '0s');
+
+ await user.click(ui.buttons.save.get());
+
+ expect(await screen.findByRole('status')).toHaveTextContent('Rule added successfully');
+ const requests = await capture;
+ const serializedRequests = await serializeRequests(requests);
+
+ // Verify that the existing group's 5m interval is preserved
+ const saveRequest = serializedRequests.find((req) => req.method === 'POST');
+ expect(saveRequest).toBeDefined();
+ expect(saveRequest?.body).toMatchObject({
+ name: grafanaRulerGroup2.name,
+ interval: '5m', // The existing group's interval should be preserved
+ rules: expect.arrayContaining([
+ expect.objectContaining({
+ annotations: expect.objectContaining({
+ description: 'some description',
+ }),
+ for: '0s',
+ }),
+ ]),
+ });
+ });
});
diff --git a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx
index e8c024d4504..c092a748388 100644
--- a/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx
+++ b/public/app/features/alerting/unified/rule-list/GrafanaGroupLoader.tsx
@@ -71,7 +71,7 @@ export function GrafanaGroupLoader({
);
}
- if (!rulerResponse || !promResponse) {
+ if (!rulerResponse && !promResponse) {
return (
- {rulerResponse.rules.map((rulerRule) => {
+ {rulerResponse?.rules.map((rulerRule) => {
const promRule = matches.get(rulerRule);
if (!promRule) {
diff --git a/public/app/features/datasources/components/DataSourcePluginSettings.tsx b/public/app/features/datasources/components/DataSourcePluginSettings.tsx
index 0dfd48bb5bf..78062064bfc 100644
--- a/public/app/features/datasources/components/DataSourcePluginSettings.tsx
+++ b/public/app/features/datasources/components/DataSourcePluginSettings.tsx
@@ -1,7 +1,7 @@
import { createElement, PureComponent } from 'react';
import { DataSourcePluginMeta, DataSourceSettings } from '@grafana/data';
-import { readOnlyCopy } from 'app/features/plugins/extensions/utils';
+import { writableProxy } from 'app/features/plugins/extensions/utils';
import { GenericDataSourcePlugin } from '../types';
@@ -34,7 +34,7 @@ export class DataSourcePluginSettings extends PureComponent {
{plugin.components.ConfigEditor &&
createElement(plugin.components.ConfigEditor, {
- options: readOnlyCopy(dataSource),
+ options: writableProxy(dataSource, { source: 'datasource', pluginId: plugin.meta?.id }),
onOptionsChange: this.onModelChanged,
})}
diff --git a/public/app/features/explore/Logs/Logs.tsx b/public/app/features/explore/Logs/Logs.tsx
index acaa32584c1..c51cbfb7d4d 100644
--- a/public/app/features/explore/Logs/Logs.tsx
+++ b/public/app/features/explore/Logs/Logs.tsx
@@ -1035,6 +1035,7 @@ const UnthemedLogs: React.FunctionComponent = (props: Props) => {
logOptionsStorageKey={SETTING_KEY_ROOT}
onLogOptionsChange={onLogOptionsChange}
hasUnescapedContent={hasUnescapedContent}
+ filterLevels={filterLevels}
/>
)}
diff --git a/public/app/features/logs/components/ControlledLogRows.tsx b/public/app/features/logs/components/ControlledLogRows.tsx
index 4ddaeb13648..189ce66dda9 100644
--- a/public/app/features/logs/components/ControlledLogRows.tsx
+++ b/public/app/features/logs/components/ControlledLogRows.tsx
@@ -7,6 +7,7 @@ import {
DataFrame,
EventBusSrv,
ExploreLogsPanelState,
+ LogLevel,
LogsMetaItem,
LogsSortOrder,
SplitOpen,
@@ -32,6 +33,7 @@ export interface ControlledLogRowsProps extends Omit