Merge branch 'main' into drclau/unistor/replace-authenticators-3
This commit is contained in:
@@ -74,6 +74,7 @@
|
|||||||
/apps/alerting/ @grafana/alerting-backend
|
/apps/alerting/ @grafana/alerting-backend
|
||||||
/pkg/api/ @grafana/grafana-backend-group
|
/pkg/api/ @grafana/grafana-backend-group
|
||||||
/pkg/apis/ @grafana/grafana-app-platform-squad
|
/pkg/apis/ @grafana/grafana-app-platform-squad
|
||||||
|
/pkg/apis/alerting_notifications @grafana/grafana-app-platform-squad @grafana/alerting-backend @grafana/alerting-frontend
|
||||||
/pkg/bus/ @grafana/grafana-search-and-storage
|
/pkg/bus/ @grafana/grafana-search-and-storage
|
||||||
/pkg/cmd/ @grafana/grafana-backend-group
|
/pkg/cmd/ @grafana/grafana-backend-group
|
||||||
/pkg/cmd/grafana/apiserver @grafana/grafana-app-platform-squad
|
/pkg/cmd/grafana/apiserver @grafana/grafana-app-platform-squad
|
||||||
@@ -101,6 +102,7 @@
|
|||||||
/pkg/middleware/ @grafana/grafana-backend-group
|
/pkg/middleware/ @grafana/grafana-backend-group
|
||||||
/pkg/mocks/ @grafana/grafana-backend-group
|
/pkg/mocks/ @grafana/grafana-backend-group
|
||||||
/pkg/models/ @grafana/grafana-backend-group
|
/pkg/models/ @grafana/grafana-backend-group
|
||||||
|
/pkg/semconv/ @grafana/grafana-backend-group
|
||||||
/pkg/server/ @grafana/grafana-backend-group
|
/pkg/server/ @grafana/grafana-backend-group
|
||||||
/pkg/apiserver @grafana/grafana-app-platform-squad
|
/pkg/apiserver @grafana/grafana-app-platform-squad
|
||||||
/pkg/apimachinery @grafana/grafana-app-platform-squad
|
/pkg/apimachinery @grafana/grafana-app-platform-squad
|
||||||
@@ -149,6 +151,7 @@
|
|||||||
/pkg/setting/ @grafana/grafana-backend-services-squad
|
/pkg/setting/ @grafana/grafana-backend-services-squad
|
||||||
/pkg/tests/ @grafana/grafana-backend-services-squad
|
/pkg/tests/ @grafana/grafana-backend-services-squad
|
||||||
/pkg/tests/apis/ @grafana/grafana-app-platform-squad
|
/pkg/tests/apis/ @grafana/grafana-app-platform-squad
|
||||||
|
/pkg/tests/apis/alerting @grafana/grafana-app-platform-squad @grafana/alerting-backend
|
||||||
/pkg/tests/api/correlations/ @grafana/explore-squad
|
/pkg/tests/api/correlations/ @grafana/explore-squad
|
||||||
/pkg/tsdb/grafanads/ @grafana/grafana-backend-group
|
/pkg/tsdb/grafanads/ @grafana/grafana-backend-group
|
||||||
/pkg/tsdb/opentsdb/ @grafana/partner-datasources
|
/pkg/tsdb/opentsdb/ @grafana/partner-datasources
|
||||||
@@ -650,6 +653,7 @@ embed.go @grafana/grafana-as-code
|
|||||||
/pkg/kinds/ @grafana/grafana-as-code
|
/pkg/kinds/ @grafana/grafana-as-code
|
||||||
/pkg/registry/ @grafana/grafana-as-code
|
/pkg/registry/ @grafana/grafana-as-code
|
||||||
/pkg/registry/apis/ @grafana/grafana-app-platform-squad
|
/pkg/registry/apis/ @grafana/grafana-app-platform-squad
|
||||||
|
/pkg/registry/apis/alerting @grafana/grafana-app-platform-squad @grafana/alerting-backend
|
||||||
/pkg/codegen/ @grafana/grafana-as-code
|
/pkg/codegen/ @grafana/grafana-as-code
|
||||||
/pkg/codegen/generators @grafana/grafana-as-code
|
/pkg/codegen/generators @grafana/grafana-as-code
|
||||||
/pkg/kinds/*/*_gen.go @grafana/grafana-as-code
|
/pkg/kinds/*/*_gen.go @grafana/grafana-as-code
|
||||||
|
|||||||
@@ -27,6 +27,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
version: v1.59.1
|
version: v1.59.1
|
||||||
args: |
|
args: |
|
||||||
--config .golangci.toml --max-same-issues=0 --max-issues-per-linter=0 --verbose ./pkg/... ./pkg/apiserver/... ./pkg/apimachinery/... ./pkg/promlib/...
|
--config .golangci.toml --max-same-issues=0 --max-issues-per-linter=0 --verbose ./pkg/... ./pkg/apiserver/... ./pkg/apimachinery/... ./pkg/promlib/... ./pkg/semconv/... ./pkg/storage/unified/resource/...
|
||||||
skip-cache: true
|
skip-cache: true
|
||||||
install-mode: binary
|
install-mode: binary
|
||||||
|
|||||||
Vendored
-894
File diff suppressed because one or more lines are too long
+925
File diff suppressed because one or more lines are too long
+1
-1
@@ -26,7 +26,7 @@ plugins:
|
|||||||
- path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs
|
- path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs
|
||||||
spec: 'https://mskelton.dev/yarn-outdated/v2'
|
spec: 'https://mskelton.dev/yarn-outdated/v2'
|
||||||
|
|
||||||
yarnPath: .yarn/releases/yarn-4.3.1.cjs
|
yarnPath: .yarn/releases/yarn-4.4.0.cjs
|
||||||
# Uncomment the following lines if you want to use Verdaccio local npm registry. Read more at packages/README.md
|
# Uncomment the following lines if you want to use Verdaccio local npm registry. Read more at packages/README.md
|
||||||
#npmScopes:
|
#npmScopes:
|
||||||
# grafana:
|
# grafana:
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ COPY pkg/build/go.* pkg/build/
|
|||||||
COPY pkg/build/wire/go.* pkg/build/wire/
|
COPY pkg/build/wire/go.* pkg/build/wire/
|
||||||
COPY pkg/promlib/go.* pkg/promlib/
|
COPY pkg/promlib/go.* pkg/promlib/
|
||||||
COPY pkg/storage/unified/resource/go.* pkg/storage/unified/resource/
|
COPY pkg/storage/unified/resource/go.* pkg/storage/unified/resource/
|
||||||
|
COPY pkg/semconv/go.* pkg/semconv/
|
||||||
|
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
RUN if [[ "$BINGO" = "true" ]]; then \
|
RUN if [[ "$BINGO" = "true" ]]; then \
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ include .bingo/Variables.mk
|
|||||||
|
|
||||||
GO = go
|
GO = go
|
||||||
GO_VERSION = 1.22.4
|
GO_VERSION = 1.22.4
|
||||||
GO_FILES ?= ./pkg/... ./pkg/apiserver/... ./pkg/apimachinery/... ./pkg/promlib/...
|
GO_FILES ?= ./pkg/... ./pkg/apiserver/... ./pkg/apimachinery/... ./pkg/promlib/... ./pkg/semconv/... ./pkg/storage/unified/resource/...
|
||||||
SH_FILES ?= $(shell find ./scripts -name *.sh)
|
SH_FILES ?= $(shell find ./scripts -name *.sh)
|
||||||
GO_RACE := $(shell [ -n "$(GO_RACE)" -o -e ".go-race-enabled-locally" ] && echo 1 )
|
GO_RACE := $(shell [ -n "$(GO_RACE)" -o -e ".go-race-enabled-locally" ] && echo 1 )
|
||||||
GO_RACE_FLAG := $(if $(GO_RACE),-race)
|
GO_RACE_FLAG := $(if $(GO_RACE),-race)
|
||||||
|
|||||||
@@ -65,6 +65,7 @@ enable_gzip = false
|
|||||||
# https certs & key file
|
# https certs & key file
|
||||||
cert_file =
|
cert_file =
|
||||||
cert_key =
|
cert_key =
|
||||||
|
cert_pass =
|
||||||
|
|
||||||
# Certificates file watch interval
|
# Certificates file watch interval
|
||||||
certs_watch_interval =
|
certs_watch_interval =
|
||||||
|
|||||||
@@ -67,6 +67,9 @@
|
|||||||
;cert_file =
|
;cert_file =
|
||||||
;cert_key =
|
;cert_key =
|
||||||
|
|
||||||
|
# optional password to be used to decrypt key file
|
||||||
|
;cert_pass =
|
||||||
|
|
||||||
# Certificates file watch interval
|
# Certificates file watch interval
|
||||||
;certs_watch_interval =
|
;certs_watch_interval =
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ Examples of labels are `server=server1` or `team=backend`. Each alert rule can h
|
|||||||
|
|
||||||
For example, an alert instance might have the label set `{alertname="High CPU usage",server="server1"}` while another alert instance might have the label set `{alertname="High CPU usage",server="server2"}`. These are two separate alert instances because although their `alertname` labels are the same, their `server` labels are different.
|
For example, an alert instance might have the label set `{alertname="High CPU usage",server="server1"}` while another alert instance might have the label set `{alertname="High CPU usage",server="server2"}`. These are two separate alert instances because although their `alertname` labels are the same, their `server` labels are different.
|
||||||
|
|
||||||
{{< figure alt="Image shows an example of an alert instance and its labels" src="/static/img/docs/alerting/unified/multi-dimensional-alert.png" >}}
|
{{< figure alt="Image shows an example of an alert instance and the labels used on the alert instance." src="/static/img/docs/alerting/unified/multi-dimensional-alert.png" >}}
|
||||||
|
|
||||||
Labels are a fundamental component of alerting:
|
Labels are a fundamental component of alerting:
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ Notification templates represent the alternative approach to templating designed
|
|||||||
Here is an example of a notification template:
|
Here is an example of a notification template:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
{ define "alerts.message" -}}
|
{{ define "alerts.message" -}}
|
||||||
{{ if .Alerts.Firing -}}
|
{{ if .Alerts.Firing -}}
|
||||||
{{ len .Alerts.Firing }} firing alert(s)
|
{{ len .Alerts.Firing }} firing alert(s)
|
||||||
{{ template "alerts.summarize" .Alerts.Firing }}
|
{{ template "alerts.summarize" .Alerts.Firing }}
|
||||||
|
|||||||
@@ -111,5 +111,3 @@ To access the State history view, complete the following steps.
|
|||||||
The value shown for each instance is for each part of the expression that was evaluated.
|
The value shown for each instance is for each part of the expression that was evaluated.
|
||||||
|
|
||||||
1. Click the labels to filter and narrow down the results.
|
1. Click the labels to filter and narrow down the results.
|
||||||
|
|
||||||
{{< figure src="/media/docs/alerting/state-history.png" max-width="750px" >}}
|
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ refs:
|
|||||||
|
|
||||||
# Configure the Tempo data source
|
# Configure the Tempo data source
|
||||||
|
|
||||||
|
The Tempo data source sets how Grafana connects to your Tempo database and lets you configure features and integrations with other telemetry signals.
|
||||||
|
|
||||||
To configure basic settings for the Tempo data source, complete the following steps:
|
To configure basic settings for the Tempo data source, complete the following steps:
|
||||||
|
|
||||||
1. Click **Connections** in the left-side menu.
|
1. Click **Connections** in the left-side menu.
|
||||||
@@ -72,7 +74,7 @@ To configure basic settings for the Tempo data source, complete the following st
|
|||||||
| **Name** | Sets the name you use to refer to the data source in panels and queries. |
|
| **Name** | Sets the name you use to refer to the data source in panels and queries. |
|
||||||
| **Default** | Sets the data source that's pre-selected for new panels. |
|
| **Default** | Sets the data source that's pre-selected for new panels. |
|
||||||
| **URL** | Sets the URL of the Tempo instance, such as `http://tempo`. |
|
| **URL** | Sets the URL of the Tempo instance, such as `http://tempo`. |
|
||||||
| **Basic Auth** | Enables basic authentication to the Tempo data source. |
|
| **Basic Auth** | Enables authentication to the Tempo data source. |
|
||||||
| **User** | Sets the user name for basic authentication. |
|
| **User** | Sets the user name for basic authentication. |
|
||||||
| **Password** | Sets the password for basic authentication. |
|
| **Password** | Sets the password for basic authentication. |
|
||||||
|
|
||||||
@@ -82,6 +84,33 @@ This video explains how to add data sources, including Loki, Tempo, and Mimir, t
|
|||||||
|
|
||||||
{{< youtube id="cqHO0oYW6Ic" start="298" >}}
|
{{< youtube id="cqHO0oYW6Ic" start="298" >}}
|
||||||
|
|
||||||
|
## Streaming
|
||||||
|
|
||||||
|
<!-- The traceQLStreaming toggle will be deprecated in Grafana 11.2 and removed in 11.3. -->
|
||||||
|
|
||||||
|
Streaming enables TraceQL query results to be displayed as they become available. Without streaming, no results are displayed until all results have returned.
|
||||||
|
|
||||||
|
{{< docs/public-preview product="TraceQL streaming results" >}}
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
To use streaming, you need to:
|
||||||
|
|
||||||
|
- Be running Tempo version 2.2 or newer, or Grafana Enterprise Traces (GET) version 2.2 or newer, or be using Grafana Cloud Traces.
|
||||||
|
- For self-managed Tempo or GET instances: If your Tempo or GET instance is behind a load balancer or proxy that doesn't supporting gRPC or HTTP2, streaming may not work and should be disabled.
|
||||||
|
|
||||||
|
### Activate streaming
|
||||||
|
|
||||||
|
For streaming to work for a particular Tempo data source, set your Grafana's `traceQLStreaming` [feature toggle](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles/) to true and set **Streaming** to enabled in your Tempo data source configuration.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
If you are using Grafana Cloud, the `traceQLStreaming` feature toggle is already set to `true` by default.
|
||||||
|
|
||||||
|
If the Tempo data source is set to allow streaming but the `traceQLStreaming` feature toggle is set to `false` in Grafana, no streaming will occur.
|
||||||
|
|
||||||
|
If the data source has streaming disabled and `traceQLStreaming` is set to `true`, no streaming will happen for that data source.
|
||||||
|
|
||||||
## Trace to logs
|
## Trace to logs
|
||||||
|
|
||||||
The **Trace to logs** setting configures [trace to logs](ref:explore-trace-integration) that's available when you integrate Grafana with Tempo.
|
The **Trace to logs** setting configures [trace to logs](ref:explore-trace-integration) that's available when you integrate Grafana with Tempo.
|
||||||
@@ -208,7 +237,8 @@ To use custom queries with the configuration, follow these steps:
|
|||||||
|
|
||||||
## Custom query variables
|
## Custom query variables
|
||||||
|
|
||||||
To use a variable in your trace to logs, metrics or profiles you need to wrap it in `${}`. For example, `${__span.name}`.
|
To use a variable in your trace to logs, metrics, or profiles, you need to wrap it in `${}`.
|
||||||
|
For example, `${__span.name}`.
|
||||||
|
|
||||||
| Variable name | Description |
|
| Variable name | Description |
|
||||||
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ labels:
|
|||||||
- oss
|
- oss
|
||||||
title: Explore Metrics
|
title: Explore Metrics
|
||||||
aliases:
|
aliases:
|
||||||
description: This topic describes the Explore Metrics feature
|
canonical: https://grafana.com/docs/grafana/latest/explore/explore-metrics/
|
||||||
|
description: Explore Metrics lets you browse Prometheus-compatible metrics using an intuitive, queryless experience.
|
||||||
weight: 200
|
weight: 200
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
|
|||||||
| `lokiQueryHints` | Enables query hints for Loki | Yes |
|
| `lokiQueryHints` | Enables query hints for Loki | Yes |
|
||||||
| `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | |
|
| `alertingQueryOptimization` | Optimizes eligible queries in order to reduce load on datasources | |
|
||||||
| `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes |
|
| `groupToNestedTableTransformation` | Enables the group to nested table transformation | Yes |
|
||||||
|
| `tlsMemcached` | Use TLS-enabled memcached in the enterprise caching feature | Yes |
|
||||||
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
|
| `cloudWatchNewLabelParsing` | Updates CloudWatch label parsing to be more accurate | Yes |
|
||||||
| `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | |
|
| `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash. | |
|
||||||
| `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes |
|
| `cloudWatchRoundUpEndTime` | Round up end time for metric queries to the next minute to avoid missing data | Yes |
|
||||||
|
|||||||
@@ -6,29 +6,15 @@ labels:
|
|||||||
title: 'Alerting Provisioning HTTP API '
|
title: 'Alerting Provisioning HTTP API '
|
||||||
---
|
---
|
||||||
|
|
||||||
The Alerting provisioning API can be used to create, modify, and delete resources relevant to [Grafana-managed alerts]({{< relref "/docs/grafana/latest/alerting/alerting-rules/create-grafana-managed-rule" >}}). It is the one used by our [Grafana Terraform provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs).
|
The Alerting Provisioning HTTP API can be used to create, modify, and delete resources relevant to Grafana-managed alerts. This API is the one used by our [Grafana Terraform provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs).
|
||||||
|
|
||||||
To manage resources related to [data source-managed alerts]({{< relref "/docs/grafana/latest/alerting/alerting-rules/create-grafana-managed-rule" >}}), including recording rules, use the [Mimir tool](https://grafana.com/docs/mimir/latest/manage/tools/mimirtool/) and [Cortex tool](https://github.com/grafana/cortex-tools#cortextool).
|
For more information on the differences between Grafana-managed and data source-managed alerts, refer to [Introduction to alert rules](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/alerting/fundamentals/alert-rules/).
|
||||||
|
|
||||||
## Information
|
> If you are running Grafana Enterprise, you need to add specific permissions for some endpoints. For more information, refer to [Role-based access control permissions](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/administration/roles-and-permissions/access-control/custom-role-actions-scopes/).
|
||||||
|
|
||||||
### Version
|
## Grafana-managed endpoints
|
||||||
|
|
||||||
1.1.0
|
Note that the JSON format from most of the following endpoints is not fully compatible with [provisioning via configuration JSON files](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/alerting/set-up/provision-alerting-resources/file-provisioning/).
|
||||||
|
|
||||||
## Content negotiation
|
|
||||||
|
|
||||||
### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
### Produces
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
- text/yaml
|
|
||||||
- application/yaml
|
|
||||||
|
|
||||||
## All endpoints
|
|
||||||
|
|
||||||
### Alert rules
|
### Alert rules
|
||||||
|
|
||||||
@@ -45,13 +31,18 @@ To manage resources related to [data source-managed alerts]({{< relref "/docs/gr
|
|||||||
| GET | /api/v1/provisioning/alert-rules | [route get alert rules](#route-get-alert-rules) | Get all the alert rules. |
|
| GET | /api/v1/provisioning/alert-rules | [route get alert rules](#route-get-alert-rules) | Get all the alert rules. |
|
||||||
| GET | /api/v1/provisioning/alert-rules/export | [route get alert rules export](#route-get-alert-rules-export) | Export all alert rules in provisioning file format. |
|
| GET | /api/v1/provisioning/alert-rules/export | [route get alert rules export](#route-get-alert-rules-export) | Export all alert rules in provisioning file format. |
|
||||||
|
|
||||||
#### Example alert rules template
|
**Example request for new alert rule:**
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/v1/provisioning/alert-rules
|
||||||
|
Accept: application/json
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
{
|
||||||
"title": "TEST-API_1",
|
"title": "TEST-API_1",
|
||||||
"ruleGroup": "API",
|
"ruleGroup": "API",
|
||||||
"folderUID": "FOLDER",
|
"folderUID": "SET_FOLDER_UID",
|
||||||
"noDataState": "OK",
|
"noDataState": "OK",
|
||||||
"execErrState": "OK",
|
"execErrState": "OK",
|
||||||
"for": "5m",
|
"for": "5m",
|
||||||
@@ -72,7 +63,7 @@ To manage resources related to [data source-managed alerts]({{< relref "/docs/gr
|
|||||||
"from": 600,
|
"from": 600,
|
||||||
"to": 0
|
"to": 0
|
||||||
},
|
},
|
||||||
"datasourceUid": " XXXXXXXXX-XXXXXXXXX-XXXXXXXXXX",
|
"datasourceUid": "XXXXXXXXX-XXXXXXXXX-XXXXXXXXXX",
|
||||||
"model": {
|
"model": {
|
||||||
"expr": "up",
|
"expr": "up",
|
||||||
"hide": false,
|
"hide": false,
|
||||||
@@ -122,6 +113,99 @@ To manage resources related to [data source-managed alerts]({{< relref "/docs/gr
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example Response:
|
||||||
|
|
||||||
|
```http
|
||||||
|
HTTP/1.1 201 Created
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"uid": "XXXXXXXXX",
|
||||||
|
"orgID": 1,
|
||||||
|
"folderUID": "SET_FOLDER_UID",
|
||||||
|
"ruleGroup": "API3",
|
||||||
|
"title": "TEST-API_1",
|
||||||
|
"condition": "B",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"refId": "A",
|
||||||
|
"queryType": "",
|
||||||
|
"relativeTimeRange": {
|
||||||
|
"from": 600,
|
||||||
|
"to": 0
|
||||||
|
},
|
||||||
|
"datasourceUid": "XXXXXXXXX-XXXXXXXXX-XXXXXXXXXX",
|
||||||
|
"model": {
|
||||||
|
"expr": "up",
|
||||||
|
"hide": false,
|
||||||
|
"intervalMs": 1000,
|
||||||
|
"maxDataPoints": 43200,
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"refId": "B",
|
||||||
|
"queryType": "",
|
||||||
|
"relativeTimeRange": {
|
||||||
|
"from": 0,
|
||||||
|
"to": 0
|
||||||
|
},
|
||||||
|
"datasourceUid": "-100",
|
||||||
|
"model": {
|
||||||
|
"conditions": [
|
||||||
|
{
|
||||||
|
"evaluator": {
|
||||||
|
"params": [
|
||||||
|
6
|
||||||
|
],
|
||||||
|
"type": "gt"
|
||||||
|
},
|
||||||
|
"operator": {
|
||||||
|
"type": "and"
|
||||||
|
},
|
||||||
|
"query": {
|
||||||
|
"params": [
|
||||||
|
"A"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"reducer": {
|
||||||
|
"params": [],
|
||||||
|
"type": "last"
|
||||||
|
},
|
||||||
|
"type": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"datasource": {
|
||||||
|
"type": "__expr__",
|
||||||
|
"uid": "-100"
|
||||||
|
},
|
||||||
|
"hide": false,
|
||||||
|
"intervalMs": 1000,
|
||||||
|
"maxDataPoints": 43200,
|
||||||
|
"refId": "B",
|
||||||
|
"type": "classic_conditions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"updated": "2024-08-02T13:19:32.609640048Z",
|
||||||
|
"noDataState": "OK",
|
||||||
|
"execErrState": "OK",
|
||||||
|
"for": "5m",
|
||||||
|
"annotations": {
|
||||||
|
"summary": "test_api_1"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"API": "test1"
|
||||||
|
},
|
||||||
|
"provenance": "api",
|
||||||
|
"isPaused": false,
|
||||||
|
"notification_settings": null,
|
||||||
|
"record": null
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Contact points
|
### Contact points
|
||||||
@@ -134,6 +218,34 @@ To manage resources related to [data source-managed alerts]({{< relref "/docs/gr
|
|||||||
| PUT | /api/v1/provisioning/contact-points/:uid | [route put contactpoint](#route-put-contactpoint) | Update an existing contact point. |
|
| PUT | /api/v1/provisioning/contact-points/:uid | [route put contactpoint](#route-put-contactpoint) | Update an existing contact point. |
|
||||||
| GET | /api/v1/provisioning/contact-points/export | [route get contactpoints export](#route-get-contactpoints-export) | Export all contact points in provisioning file format. |
|
| GET | /api/v1/provisioning/contact-points/export | [route get contactpoints export](#route-get-contactpoints-export) | Export all contact points in provisioning file format. |
|
||||||
|
|
||||||
|
**Example Request for all the contact points:**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/provisioning/contact-points
|
||||||
|
Accept: application/json
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Response:**
|
||||||
|
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"uid": "",
|
||||||
|
"name": "email receiver",
|
||||||
|
"type": "email",
|
||||||
|
"settings": {
|
||||||
|
"addresses": "<example@email.com>"
|
||||||
|
},
|
||||||
|
"disableResolveMessage": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
### Notification policies
|
### Notification policies
|
||||||
|
|
||||||
| Method | URI | Name | Summary |
|
| Method | URI | Name | Summary |
|
||||||
@@ -143,6 +255,38 @@ To manage resources related to [data source-managed alerts]({{< relref "/docs/gr
|
|||||||
| PUT | /api/v1/provisioning/policies | [route put policy tree](#route-put-policy-tree) | Sets the notification policy tree. |
|
| PUT | /api/v1/provisioning/policies | [route put policy tree](#route-put-policy-tree) | Sets the notification policy tree. |
|
||||||
| GET | /api/v1/provisioning/policies/export | [route get policy tree export](#route-get-policy-tree-export) | Export the notification policy tree in provisioning file format. |
|
| GET | /api/v1/provisioning/policies/export | [route get policy tree export](#route-get-policy-tree-export) | Export the notification policy tree in provisioning file format. |
|
||||||
|
|
||||||
|
**Example Request for exporting the notification policy tree in YAML format:**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/provisioning/policies/export?format=yaml
|
||||||
|
Accept: application/json
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Response:**
|
||||||
|
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: text/yaml
|
||||||
|
|
||||||
|
apiVersion: 1
|
||||||
|
policies:
|
||||||
|
- orgId: 1
|
||||||
|
receiver: My Contact Email Point
|
||||||
|
group_by:
|
||||||
|
- grafana_folder
|
||||||
|
- alertname
|
||||||
|
routes:
|
||||||
|
- receiver: My Contact Email Point
|
||||||
|
object_matchers:
|
||||||
|
- - monitor
|
||||||
|
- =
|
||||||
|
- testdata
|
||||||
|
mute_time_intervals:
|
||||||
|
- weekends
|
||||||
|
```
|
||||||
|
|
||||||
### Mute timings
|
### Mute timings
|
||||||
|
|
||||||
| Method | URI | Name | Summary |
|
| Method | URI | Name | Summary |
|
||||||
@@ -155,6 +299,38 @@ To manage resources related to [data source-managed alerts]({{< relref "/docs/gr
|
|||||||
| GET | /api/v1/provisioning/mute-timings/export | [route get mute timings export](#route-get-mute-timings-export) | Export all mute timings in provisioning file format. |
|
| GET | /api/v1/provisioning/mute-timings/export | [route get mute timings export](#route-get-mute-timings-export) | Export all mute timings in provisioning file format. |
|
||||||
| GET | /api/v1/provisioning/mute-timings/:name/export | [route get mute timing export](#route-get-mute-timing-export) | Export a mute timing in provisioning file format. |
|
| GET | /api/v1/provisioning/mute-timings/:name/export | [route get mute timing export](#route-get-mute-timing-export) | Export a mute timing in provisioning file format. |
|
||||||
|
|
||||||
|
**Example Request for all mute timings:**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/provisioning/mute-timings
|
||||||
|
Accept: application/json
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Response:**
|
||||||
|
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "weekends",
|
||||||
|
"time_intervals": [
|
||||||
|
{
|
||||||
|
"weekdays": [
|
||||||
|
"saturday",
|
||||||
|
"sunday"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"version": "",
|
||||||
|
"provenance": "file"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
### Templates
|
### Templates
|
||||||
|
|
||||||
| Method | URI | Name | Summary |
|
| Method | URI | Name | Summary |
|
||||||
@@ -164,7 +340,36 @@ To manage resources related to [data source-managed alerts]({{< relref "/docs/gr
|
|||||||
| GET | /api/v1/provisioning/templates | [route get templates](#route-get-templates) | Get all notification templates. |
|
| GET | /api/v1/provisioning/templates | [route get templates](#route-get-templates) | Get all notification templates. |
|
||||||
| PUT | /api/v1/provisioning/templates/:name | [route put template](#route-put-template) | Create or update a notification template. |
|
| PUT | /api/v1/provisioning/templates/:name | [route put template](#route-put-template) | Create or update a notification template. |
|
||||||
|
|
||||||
## Edit resources in the Grafana UI
|
**Example Request for all notification templates:**
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/provisioning/templates
|
||||||
|
Accept: application/json
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example Response:**
|
||||||
|
|
||||||
|
```http
|
||||||
|
HTTP/1.1 200 OK
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "custom_email.message",
|
||||||
|
"template": "{{ define \"custom_email.message\" }}\n Custom alert!\n{{ end }}",
|
||||||
|
"provenance": "file"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "custom_email.subject",
|
||||||
|
"template": "{{ define \"custom_email.subject\" }}\n{{ len .Alerts.Firing }} firing alert(s), {{ len .Alerts.Resolved }} resolved alert(s)\n{{ end }}",
|
||||||
|
"provenance": "file"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Edit resources in the Grafana UI
|
||||||
|
|
||||||
By default, you cannot edit API-provisioned alerting resources in Grafana. To enable editing these resources in the Grafana UI, add the `X-Disable-Provenance` header to the following requests in the API:
|
By default, you cannot edit API-provisioned alerting resources in Grafana. To enable editing these resources in the Grafana UI, add the `X-Disable-Provenance` header to the following requests in the API:
|
||||||
|
|
||||||
@@ -177,6 +382,14 @@ By default, you cannot edit API-provisioned alerting resources in Grafana. To en
|
|||||||
|
|
||||||
To reset the notification policy tree to the default and unlock it for editing in the Grafana UI, use the `DELETE /api/v1/provisioning/policies` endpoint.
|
To reset the notification policy tree to the default and unlock it for editing in the Grafana UI, use the `DELETE /api/v1/provisioning/policies` endpoint.
|
||||||
|
|
||||||
|
## Data source-managed resources
|
||||||
|
|
||||||
|
The Alerting Provisioning HTTP API can only be used to manage Grafana-managed alert resources. To manage resources related to [data source-managed alerts](https://grafana.com/docs/grafana/<GRAFANA_VERSION>/alerting/alerting-rules/create-mimir-loki-managed-rule/), consider the following tools:
|
||||||
|
|
||||||
|
- [mimirtool](https://grafana.com/docs/mimir/<GRAFANA_VERSION>/manage/tools/mimirtool/): to interact with the Mimir alertmanager and ruler configuration.
|
||||||
|
- [cortex-tools](https://github.com/grafana/cortex-tools#cortextool): to interact with the Cortex alertmanager and ruler configuration.
|
||||||
|
- [lokitool](https://grafana.com/docs/loki/<GRAFANA_VERSION>/alert/#lokitool): to configure the Loki Ruler.
|
||||||
|
|
||||||
## Paths
|
## Paths
|
||||||
|
|
||||||
### <span id="route-delete-alert-rule"></span> Delete a specific alert rule by UID. (_RouteDeleteAlertRule_)
|
### <span id="route-delete-alert-rule"></span> Delete a specific alert rule by UID. (_RouteDeleteAlertRule_)
|
||||||
@@ -216,10 +429,6 @@ Status: No Content
|
|||||||
DELETE /api/v1/provisioning/contact-points/:uid
|
DELETE /api/v1/provisioning/contact-points/:uid
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
| Name | Source | Type | Go type | Separator | Required | Default | Description |
|
| Name | Source | Type | Go type | Separator | Required | Default | Description |
|
||||||
@@ -884,10 +1093,6 @@ Status: Not Found
|
|||||||
POST /api/v1/provisioning/alert-rules
|
POST /api/v1/provisioning/alert-rules
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
{{% responsive-table %}}
|
{{% responsive-table %}}
|
||||||
@@ -930,10 +1135,6 @@ Status: Bad Request
|
|||||||
POST /api/v1/provisioning/contact-points
|
POST /api/v1/provisioning/contact-points
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
{{% responsive-table %}}
|
{{% responsive-table %}}
|
||||||
@@ -976,10 +1177,6 @@ Status: Bad Request
|
|||||||
POST /api/v1/provisioning/mute-timings
|
POST /api/v1/provisioning/mute-timings
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
{{% responsive-table %}}
|
{{% responsive-table %}}
|
||||||
@@ -1022,10 +1219,6 @@ Status: Bad Request
|
|||||||
PUT /api/v1/provisioning/alert-rules/:uid
|
PUT /api/v1/provisioning/alert-rules/:uid
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
{{% responsive-table %}}
|
{{% responsive-table %}}
|
||||||
@@ -1069,10 +1262,6 @@ Status: Bad Request
|
|||||||
PUT /api/v1/provisioning/folder/:folderUid/rule-groups/:group
|
PUT /api/v1/provisioning/folder/:folderUid/rule-groups/:group
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
{{% responsive-table %}}
|
{{% responsive-table %}}
|
||||||
@@ -1117,10 +1306,6 @@ Status: Bad Request
|
|||||||
PUT /api/v1/provisioning/contact-points/:uid
|
PUT /api/v1/provisioning/contact-points/:uid
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
{{% responsive-table %}}
|
{{% responsive-table %}}
|
||||||
@@ -1164,10 +1349,6 @@ Status: Bad Request
|
|||||||
PUT /api/v1/provisioning/mute-timings/:name
|
PUT /api/v1/provisioning/mute-timings/:name
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
{{% responsive-table %}}
|
{{% responsive-table %}}
|
||||||
@@ -1211,10 +1392,6 @@ Status: Bad Request
|
|||||||
PUT /api/v1/provisioning/policies
|
PUT /api/v1/provisioning/policies
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
|
|
||||||
{{% responsive-table %}}
|
{{% responsive-table %}}
|
||||||
@@ -1257,10 +1434,6 @@ Status: Bad Request
|
|||||||
PUT /api/v1/provisioning/templates/:name
|
PUT /api/v1/provisioning/templates/:name
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
{{% responsive-table %}}
|
{{% responsive-table %}}
|
||||||
|
|
||||||
#### Parameters
|
#### Parameters
|
||||||
@@ -1304,10 +1477,6 @@ Status: Bad Request
|
|||||||
DELETE /api/v1/provisioning/policies
|
DELETE /api/v1/provisioning/policies
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Consumes
|
|
||||||
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
#### All responses
|
#### All responses
|
||||||
|
|
||||||
| Code | Status | Description | Has headers | Schema |
|
| Code | Status | Description | Has headers | Schema |
|
||||||
|
|||||||
@@ -161,9 +161,13 @@ Queries can take a little while to return results. The results appear in a table
|
|||||||
The Tempo data source supports streaming responses to TraceQL queries so you can see partial query results as they come in without waiting for the whole query to finish.
|
The Tempo data source supports streaming responses to TraceQL queries so you can see partial query results as they come in without waiting for the whole query to finish.
|
||||||
|
|
||||||
{{% admonition type="note" %}}
|
{{% admonition type="note" %}}
|
||||||
To use this experimental feature, enable the `traceQLStreaming` feature toggle. If you’re using Grafana Cloud and would like to enable this feature, please contact customer support.
|
To use this public preview feature, enable the `traceQLStreaming` feature toggle.
|
||||||
|
When active, all configured Tempo data sources will attempt to use streaming.
|
||||||
|
You can control which Tempo data sources do and don't attempt to stream results at the per-data source level using the **Streaming** section of the Tempo data source configuration.
|
||||||
|
|
||||||
{{% /admonition %}}
|
{{% /admonition %}}
|
||||||
|
|
||||||
Streaming is available for both the **Search** and **TraceQL** query types, and you'll get immediate visibility of incoming traces on the results table.
|
Streaming is available for both the **Search** and **TraceQL** query types.
|
||||||
|
You'll get immediate visibility of incoming traces on the results table.
|
||||||
|
|
||||||
{{< video-embed src="/media/docs/grafana/data-sources/tempo-streaming-v2.mp4" >}}
|
{{< video-embed src="/media/docs/grafana/data-sources/tempo-streaming-v2.mp4" >}}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ require (
|
|||||||
github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics
|
github.com/grafana/dataplane/sdata v0.0.9 // @grafana/observability-metrics
|
||||||
github.com/grafana/dskit v0.0.0-20240311184239-73feada6c0d7 // @grafana/grafana-backend-group
|
github.com/grafana/dskit v0.0.0-20240311184239-73feada6c0d7 // @grafana/grafana-backend-group
|
||||||
github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad
|
github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad
|
||||||
github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56 // @grafana/grafana-operator-experience-squad
|
github.com/grafana/gomemcache v0.0.0-20240805133030-fdaf6a95408e // @grafana/grafana-operator-experience-squad
|
||||||
github.com/grafana/grafana-aws-sdk v0.30.0 // @grafana/aws-datasources
|
github.com/grafana/grafana-aws-sdk v0.30.0 // @grafana/aws-datasources
|
||||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.0 // @grafana/partner-datasources
|
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.0 // @grafana/partner-datasources
|
||||||
github.com/grafana/grafana-cloud-migration-snapshot v1.2.0 // @grafana/grafana-operator-experience-squad
|
github.com/grafana/grafana-cloud-migration-snapshot v1.2.0 // @grafana/grafana-operator-experience-squad
|
||||||
@@ -472,7 +472,10 @@ require (
|
|||||||
|
|
||||||
require github.com/phpdave11/gofpdi v1.0.13 // @grafana/sharing-squad
|
require github.com/phpdave11/gofpdi v1.0.13 // @grafana/sharing-squad
|
||||||
|
|
||||||
require github.com/google/go-querystring v1.1.0 // indirect
|
require (
|
||||||
|
github.com/google/go-querystring v1.1.0 // indirect; @grafana/observability-metrics
|
||||||
|
github.com/grafana/e2e v0.1.1 // indirect; @grafana-app-platform-squad
|
||||||
|
)
|
||||||
|
|
||||||
// Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream
|
// Use fork of crewjam/saml with fixes for some issues until changes get merged into upstream
|
||||||
replace github.com/crewjam/saml => github.com/grafana/saml v0.4.15-0.20240523142256-cc370b98af7c
|
replace github.com/crewjam/saml => github.com/grafana/saml v0.4.15-0.20240523142256-cc370b98af7c
|
||||||
|
|||||||
@@ -2320,10 +2320,28 @@ github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6k
|
|||||||
github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU=
|
github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU=
|
||||||
github.com/grafana/dskit v0.0.0-20240311184239-73feada6c0d7 h1:yd9yoNgEOtp8O0MbtqXoMVqr+ZbU4oZFE8a04z8WXFE=
|
github.com/grafana/dskit v0.0.0-20240311184239-73feada6c0d7 h1:yd9yoNgEOtp8O0MbtqXoMVqr+ZbU4oZFE8a04z8WXFE=
|
||||||
github.com/grafana/dskit v0.0.0-20240311184239-73feada6c0d7/go.mod h1:RpTvZ9nkdXqyQro5DULQHJl9B6vwvEj95Dk6WIXqTLQ=
|
github.com/grafana/dskit v0.0.0-20240311184239-73feada6c0d7/go.mod h1:RpTvZ9nkdXqyQro5DULQHJl9B6vwvEj95Dk6WIXqTLQ=
|
||||||
|
github.com/grafana/e2e v0.1.1 h1:/b6xcv5BtoBnx8cZnCiey9DbjEc8z7gXHO5edoeRYxc=
|
||||||
|
github.com/grafana/e2e v0.1.1/go.mod h1:RpNLgae5VT+BUHvPE+/zSypmOXKwEu4t+tnEMS1ATaE=
|
||||||
github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 h1:jxJJ5z0GxqhWFbQUsys3BHG8jnmniJ2Q74tXAG1NaDo=
|
github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 h1:jxJJ5z0GxqhWFbQUsys3BHG8jnmniJ2Q74tXAG1NaDo=
|
||||||
github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447/go.mod h1:IxsY6mns6Q5sAnWcrptrgUrSglTZJXH/kXr9nbpb/9I=
|
github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447/go.mod h1:IxsY6mns6Q5sAnWcrptrgUrSglTZJXH/kXr9nbpb/9I=
|
||||||
github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56 h1:X8IKQ0wu40wpvYcKfBcc5T4QnhdQjUhtUtB/1CY89lE=
|
github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56 h1:X8IKQ0wu40wpvYcKfBcc5T4QnhdQjUhtUtB/1CY89lE=
|
||||||
github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU=
|
github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240801211247-70ab5c5d3cfe h1:wLBInGo6gCp/gmgyVpk76RtSbRpjiic5XZF1CwEsNPs=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240801211247-70ab5c5d3cfe/go.mod h1:IGRj8oOoxwJbHBYl1+OhS9UjQR0dv6SQOep7HqmtyFU=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240802030650-a3dfe179e761 h1:IkOfxkgOwVRun96IWwqH1Qgb+FSOz5GEHz0+DPHKAVk=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240802030650-a3dfe179e761/go.mod h1:IGRj8oOoxwJbHBYl1+OhS9UjQR0dv6SQOep7HqmtyFU=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240802031600-ac7e88487c0b h1:UAc4nkD8lSdSxuusEV9s/1h9VrIOD8GwPNd1PEQR8+4=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240802031600-ac7e88487c0b/go.mod h1:IGRj8oOoxwJbHBYl1+OhS9UjQR0dv6SQOep7HqmtyFU=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240802032403-45805b5fe293 h1:5CffDaSN2oArwTdUYfwX9YMxsUUTaJMoCeNy9kE2HCM=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240802032403-45805b5fe293/go.mod h1:IGRj8oOoxwJbHBYl1+OhS9UjQR0dv6SQOep7HqmtyFU=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240802032810-8f21fdbcb39d h1:f6+eE8zygAcjBs761JSuSfSHHzW0PdMFahljsTijw+w=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240802032810-8f21fdbcb39d/go.mod h1:IGRj8oOoxwJbHBYl1+OhS9UjQR0dv6SQOep7HqmtyFU=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240802041632-201cbbb2bceb h1:+ZqYDh5tQqppvqvmi+u4A+H/1tgXacV7MeEijPqPSj4=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240802041632-201cbbb2bceb/go.mod h1:IGRj8oOoxwJbHBYl1+OhS9UjQR0dv6SQOep7HqmtyFU=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240803024453-e764d08ea2ed h1:tVRLkqWy0qJOpZRwdwqDpKoJyH3uTnJ4w3JwrVX5Ac4=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240803024453-e764d08ea2ed/go.mod h1:IGRj8oOoxwJbHBYl1+OhS9UjQR0dv6SQOep7HqmtyFU=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240805133030-fdaf6a95408e h1:UlEET0InuoFautfaFp8lDrNF7rPHYXuBMrzwWx9XqFY=
|
||||||
|
github.com/grafana/gomemcache v0.0.0-20240805133030-fdaf6a95408e/go.mod h1:IGRj8oOoxwJbHBYl1+OhS9UjQR0dv6SQOep7HqmtyFU=
|
||||||
github.com/grafana/grafana-aws-sdk v0.30.0 h1:6IIetM4s2NbvPOI4/fefsyN84BIb0/T09lHGF/pywo8=
|
github.com/grafana/grafana-aws-sdk v0.30.0 h1:6IIetM4s2NbvPOI4/fefsyN84BIb0/T09lHGF/pywo8=
|
||||||
github.com/grafana/grafana-aws-sdk v0.30.0/go.mod h1:ZSVPU7IIJSi5lEg+K3Js+EUpZLXxUaBdaQWH+As1ihI=
|
github.com/grafana/grafana-aws-sdk v0.30.0/go.mod h1:ZSVPU7IIJSi5lEg+K3Js+EUpZLXxUaBdaQWH+As1ihI=
|
||||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.0 h1:lajVqTWaE96MpbjZToj7EshvqgRWOfYNkD4MbIZizaY=
|
github.com/grafana/grafana-azure-sdk-go/v2 v2.1.0 h1:lajVqTWaE96MpbjZToj7EshvqgRWOfYNkD4MbIZizaY=
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use (
|
|||||||
./pkg/build
|
./pkg/build
|
||||||
./pkg/build/wire
|
./pkg/build/wire
|
||||||
./pkg/promlib
|
./pkg/promlib
|
||||||
|
./pkg/semconv
|
||||||
./pkg/storage/unified/resource
|
./pkg/storage/unified/resource
|
||||||
./pkg/util/xorm
|
./pkg/util/xorm
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -315,6 +315,8 @@ github.com/eapache/go-resiliency v1.6.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6
|
|||||||
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
|
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws=
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
|
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
|
||||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||||
|
github.com/efficientgo/tools/core v0.0.0-20220225185207-fe763185946b h1:ZHiD4/yE4idlbqvAO6iYCOYRzOMRpxkW+FKasRA3tsQ=
|
||||||
|
github.com/efficientgo/tools/core v0.0.0-20220225185207-fe763185946b/go.mod h1:OmVcnJopJL8d3X3sSXTiypGoUSgFq1aDGmlrdi9dn/M=
|
||||||
github.com/elastic/go-sysinfo v1.11.2 h1:mcm4OSYVMyws6+n2HIVMGkln5HOpo5Ie1ZmbbNn0jg4=
|
github.com/elastic/go-sysinfo v1.11.2 h1:mcm4OSYVMyws6+n2HIVMGkln5HOpo5Ie1ZmbbNn0jg4=
|
||||||
github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ=
|
github.com/elastic/go-sysinfo v1.11.2/go.mod h1:GKqR8bbMK/1ITnez9NIsIfXQr25aLhRJa7AfT8HpBFQ=
|
||||||
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
||||||
@@ -484,6 +486,8 @@ github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3ro
|
|||||||
github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY=
|
github.com/kisielk/errcheck v1.5.0 h1:e8esj/e4R+SAOwFwN+n3zr0nYeCyeweozKfO23MvHzY=
|
||||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||||
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw2e+eeNT/SbGySq8ajECXJ9e4fPoLhY=
|
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46 h1:veS9QfglfvqAw2e+eeNT/SbGySq8ajECXJ9e4fPoLhY=
|
||||||
|
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
|
||||||
|
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
|
||||||
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
|
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
|
||||||
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
||||||
github.com/knadh/koanf/v2 v2.1.0 h1:eh4QmHHBuU8BybfIJ8mB8K8gsGCD/AUQTdwGq/GzId8=
|
github.com/knadh/koanf/v2 v2.1.0 h1:eh4QmHHBuU8BybfIJ8mB8K8gsGCD/AUQTdwGq/GzId8=
|
||||||
@@ -661,6 +665,8 @@ github.com/tdewolff/minify/v2 v2.12.9 h1:dvn5MtmuQ/DFMwqf5j8QhEVpPX6fi3WGImhv8RU
|
|||||||
github.com/tdewolff/minify/v2 v2.12.9/go.mod h1:qOqdlDfL+7v0/fyymB+OP497nIxJYSvX4MQWA8OoiXU=
|
github.com/tdewolff/minify/v2 v2.12.9/go.mod h1:qOqdlDfL+7v0/fyymB+OP497nIxJYSvX4MQWA8OoiXU=
|
||||||
github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA=
|
github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvFMA=
|
||||||
github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM=
|
github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM=
|
||||||
|
github.com/thanos-io/objstore v0.0.0-20220809103346-8ef1f215e2bf h1:onQsPyHlq2yIWU+Nfl6yStuqnZuVQQN8FZ8sBb2wqtw=
|
||||||
|
github.com/thanos-io/objstore v0.0.0-20220809103346-8ef1f215e2bf/go.mod h1:v0NhuxxxUFUPatQcVNSCUkBEVezXzl7LSdaBOZygq98=
|
||||||
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U=
|
||||||
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||||
@@ -829,6 +835,8 @@ golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhp
|
|||||||
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
|
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||||
|
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
|
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk=
|
||||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
set -o nounset
|
set -o nounset
|
||||||
@@ -8,11 +8,12 @@ rm -rf data/grafana-aggregator
|
|||||||
|
|
||||||
mkdir -p data/grafana-aggregator
|
mkdir -p data/grafana-aggregator
|
||||||
|
|
||||||
openssl req -nodes -new -x509 -keyout data/grafana-aggregator/ca.key -out data/grafana-aggregator/ca.crt
|
openssl req -nodes -new -x509 -keyout data/grafana-aggregator/ca.key -out data/grafana-aggregator/ca.crt \
|
||||||
|
-subj "/C=US/ST=New Sweden/L=Stockholm /O=Grafana/OU=R&D/CN=test-ca/emailAddress=test@grafana.app" -days 3650
|
||||||
openssl req -out data/grafana-aggregator/client.csr -new -newkey rsa:4096 -nodes -keyout data/grafana-aggregator/client.key \
|
openssl req -out data/grafana-aggregator/client.csr -new -newkey rsa:4096 -nodes -keyout data/grafana-aggregator/client.key \
|
||||||
-subj "/CN=development/O=system:masters" \
|
-subj "/CN=development/O=system:masters" \
|
||||||
-addext "extendedKeyUsage = clientAuth"
|
-addext "extendedKeyUsage = clientAuth"
|
||||||
openssl x509 -req -days 365 -in data/grafana-aggregator/client.csr -CA data/grafana-aggregator/ca.crt -CAkey data/grafana-aggregator/ca.key \
|
openssl x509 -req -days 3650 -in data/grafana-aggregator/client.csr -CA data/grafana-aggregator/ca.crt -CAkey data/grafana-aggregator/ca.key \
|
||||||
-set_serial 01 \
|
-set_serial 01 \
|
||||||
-sha256 -out data/grafana-aggregator/client.crt \
|
-sha256 -out data/grafana-aggregator/client.crt \
|
||||||
-copy_extensions=copyall
|
-copy_extensions=copyall
|
||||||
@@ -21,7 +22,11 @@ openssl req -out data/grafana-aggregator/server.csr -new -newkey rsa:4096 -nodes
|
|||||||
-subj "/CN=localhost/O=aggregated" \
|
-subj "/CN=localhost/O=aggregated" \
|
||||||
-addext "subjectAltName = DNS:v0alpha1.example.grafana.app.default.svc,DNS:localhost" \
|
-addext "subjectAltName = DNS:v0alpha1.example.grafana.app.default.svc,DNS:localhost" \
|
||||||
-addext "extendedKeyUsage = serverAuth, clientAuth"
|
-addext "extendedKeyUsage = serverAuth, clientAuth"
|
||||||
openssl x509 -req -days 365 -in data/grafana-aggregator/server.csr -CA data/grafana-aggregator/ca.crt -CAkey data/grafana-aggregator/ca.key \
|
openssl x509 -req -days 3650 -in data/grafana-aggregator/server.csr -CA data/grafana-aggregator/ca.crt -CAkey data/grafana-aggregator/ca.key \
|
||||||
-set_serial 02 \
|
-set_serial 02 \
|
||||||
-sha256 -out data/grafana-aggregator/server.crt \
|
-sha256 -out data/grafana-aggregator/server.crt \
|
||||||
-copy_extensions=copyall
|
-copy_extensions=copyall
|
||||||
|
|
||||||
|
# Apply broad permissions to certificates/keys so that containers passing these around for
|
||||||
|
# tests don't run into permission related errors
|
||||||
|
chmod 755 data/grafana-aggregator/*.*
|
||||||
|
|||||||
+8
-7
@@ -148,10 +148,10 @@
|
|||||||
"@types/uuid": "9.0.8",
|
"@types/uuid": "9.0.8",
|
||||||
"@types/webpack-assets-manifest": "^5",
|
"@types/webpack-assets-manifest": "^5",
|
||||||
"@types/webpack-env": "^1.18.4",
|
"@types/webpack-env": "^1.18.4",
|
||||||
"@types/yargs": "17.0.32",
|
"@types/yargs": "17.0.33",
|
||||||
"@typescript-eslint/eslint-plugin": "6.21.0",
|
"@typescript-eslint/eslint-plugin": "6.21.0",
|
||||||
"@typescript-eslint/parser": "6.21.0",
|
"@typescript-eslint/parser": "6.21.0",
|
||||||
"autoprefixer": "10.4.19",
|
"autoprefixer": "10.4.20",
|
||||||
"babel-loader": "9.1.3",
|
"babel-loader": "9.1.3",
|
||||||
"blob-polyfill": "7.0.20220408",
|
"blob-polyfill": "7.0.20220408",
|
||||||
"browserslist": "^4.21.4",
|
"browserslist": "^4.21.4",
|
||||||
@@ -172,6 +172,7 @@
|
|||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-jest": "28.6.0",
|
"eslint-plugin-jest": "28.6.0",
|
||||||
|
"eslint-plugin-jest-dom": "^5.4.0",
|
||||||
"eslint-plugin-jsdoc": "48.11.0",
|
"eslint-plugin-jsdoc": "48.11.0",
|
||||||
"eslint-plugin-jsx-a11y": "6.9.0",
|
"eslint-plugin-jsx-a11y": "6.9.0",
|
||||||
"eslint-plugin-lodash": "7.4.0",
|
"eslint-plugin-lodash": "7.4.0",
|
||||||
@@ -197,14 +198,14 @@
|
|||||||
"jest-matcher-utils": "29.7.0",
|
"jest-matcher-utils": "29.7.0",
|
||||||
"jest-watch-typeahead": "^2.2.2",
|
"jest-watch-typeahead": "^2.2.2",
|
||||||
"knip": "^5.10.0",
|
"knip": "^5.10.0",
|
||||||
"lerna": "8.1.7",
|
"lerna": "8.1.8",
|
||||||
"mini-css-extract-plugin": "2.9.0",
|
"mini-css-extract-plugin": "2.9.0",
|
||||||
"msw": "2.3.5",
|
"msw": "2.3.5",
|
||||||
"mutationobserver-shim": "0.3.7",
|
"mutationobserver-shim": "0.3.7",
|
||||||
"ngtemplate-loader": "2.1.0",
|
"ngtemplate-loader": "2.1.0",
|
||||||
"node-notifier": "10.0.1",
|
"node-notifier": "10.0.1",
|
||||||
"nx": "19.2.0",
|
"nx": "19.2.0",
|
||||||
"postcss": "8.4.40",
|
"postcss": "8.4.41",
|
||||||
"postcss-loader": "8.1.1",
|
"postcss-loader": "8.1.1",
|
||||||
"postcss-reporter": "7.1.0",
|
"postcss-reporter": "7.1.0",
|
||||||
"postcss-scss": "4.0.9",
|
"postcss-scss": "4.0.9",
|
||||||
@@ -215,7 +216,7 @@
|
|||||||
"react-test-renderer": "18.2.0",
|
"react-test-renderer": "18.2.0",
|
||||||
"redux-mock-store": "1.5.4",
|
"redux-mock-store": "1.5.4",
|
||||||
"rimraf": "5.0.7",
|
"rimraf": "5.0.7",
|
||||||
"rudder-sdk-js": "2.48.14",
|
"rudder-sdk-js": "2.48.15",
|
||||||
"sass": "1.77.8",
|
"sass": "1.77.8",
|
||||||
"sass-loader": "14.2.1",
|
"sass-loader": "14.2.1",
|
||||||
"smtp-tester": "^2.1.0",
|
"smtp-tester": "^2.1.0",
|
||||||
@@ -243,7 +244,7 @@
|
|||||||
"@emotion/css": "11.11.2",
|
"@emotion/css": "11.11.2",
|
||||||
"@emotion/react": "11.11.4",
|
"@emotion/react": "11.11.4",
|
||||||
"@fingerprintjs/fingerprintjs": "^3.4.2",
|
"@fingerprintjs/fingerprintjs": "^3.4.2",
|
||||||
"@floating-ui/react": "0.26.20",
|
"@floating-ui/react": "0.26.21",
|
||||||
"@formatjs/intl-durationformat": "^0.2.4",
|
"@formatjs/intl-durationformat": "^0.2.4",
|
||||||
"@glideapps/glide-data-grid": "^6.0.0",
|
"@glideapps/glide-data-grid": "^6.0.0",
|
||||||
"@grafana/aws-sdk": "0.4.1",
|
"@grafana/aws-sdk": "0.4.1",
|
||||||
@@ -432,7 +433,7 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 20"
|
"node": ">= 20"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.3.1",
|
"packageManager": "yarn@4.4.0",
|
||||||
"dependenciesMeta": {
|
"dependenciesMeta": {
|
||||||
"prettier@3.3.3": {
|
"prettier@3.3.3": {
|
||||||
"unplugged": true
|
"unplugged": true
|
||||||
|
|||||||
@@ -55,14 +55,13 @@ describe('Stats Calculators', () => {
|
|||||||
it('should calculate basic stats', () => {
|
it('should calculate basic stats', () => {
|
||||||
const stats = reduceField({
|
const stats = reduceField({
|
||||||
field: basicTable.fields[0],
|
field: basicTable.fields[0],
|
||||||
reducers: [ReducerID.first, ReducerID.last, ReducerID.mean, ReducerID.count, ReducerID.diffperc],
|
reducers: [ReducerID.first, ReducerID.last, ReducerID.mean, ReducerID.count],
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(stats.first).toEqual(10);
|
expect(stats.first).toEqual(10);
|
||||||
expect(stats.last).toEqual(20);
|
expect(stats.last).toEqual(20);
|
||||||
expect(stats.mean).toEqual(15);
|
expect(stats.mean).toEqual(15);
|
||||||
expect(stats.count).toEqual(2);
|
expect(stats.count).toEqual(2);
|
||||||
expect(stats.diffperc).toEqual(100);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle undefined field data without crashing', () => {
|
it('should handle undefined field data without crashing', () => {
|
||||||
|
|||||||
@@ -582,7 +582,7 @@ export function doStandardCalcs(field: Field, ignoreNulls: boolean, nullAsZero:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isNumber(calcs.firstNotNull) && isNumber(calcs.diff)) {
|
if (isNumber(calcs.firstNotNull) && isNumber(calcs.diff)) {
|
||||||
calcs.diffperc = (calcs.diff / calcs.firstNotNull) * 100;
|
calcs.diffperc = calcs.diff / calcs.firstNotNull;
|
||||||
}
|
}
|
||||||
return calcs;
|
return calcs;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,5 +20,5 @@
|
|||||||
"typescript": "5.4.5",
|
"typescript": "5.4.5",
|
||||||
"webpack": "5.91.0"
|
"webpack": "5.91.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@4.3.1"
|
"packageManager": "yarn@4.4.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/css": "11.11.2",
|
"@emotion/css": "11.11.2",
|
||||||
"@floating-ui/react": "0.26.20",
|
"@floating-ui/react": "0.26.21",
|
||||||
"@grafana/data": "11.2.0-pre",
|
"@grafana/data": "11.2.0-pre",
|
||||||
"@grafana/experimental": "1.7.13",
|
"@grafana/experimental": "1.7.13",
|
||||||
"@grafana/faro-web-sdk": "1.9.0",
|
"@grafana/faro-web-sdk": "1.9.0",
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/css": "11.11.2",
|
"@emotion/css": "11.11.2",
|
||||||
"@emotion/react": "11.11.4",
|
"@emotion/react": "11.11.4",
|
||||||
"@floating-ui/react": "0.26.20",
|
"@floating-ui/react": "0.26.21",
|
||||||
"@grafana/data": "11.2.0-pre",
|
"@grafana/data": "11.2.0-pre",
|
||||||
"@grafana/e2e-selectors": "11.2.0-pre",
|
"@grafana/e2e-selectors": "11.2.0-pre",
|
||||||
"@grafana/faro-web-sdk": "^1.3.6",
|
"@grafana/faro-web-sdk": "^1.3.6",
|
||||||
|
|||||||
+4
-1
@@ -166,11 +166,14 @@ export const DataLinksInlineEditor = ({
|
|||||||
const getDataLinksInlineEditorStyles = (theme: GrafanaTheme2) => ({
|
const getDataLinksInlineEditorStyles = (theme: GrafanaTheme2) => ({
|
||||||
wrapper: css({
|
wrapper: css({
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 16,
|
||||||
}),
|
}),
|
||||||
oneClickOverlay: css({
|
oneClickOverlay: css({
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
|
minHeight: 69,
|
||||||
border: `1px dashed ${theme.colors.border.medium}`,
|
border: `1px dashed ${theme.colors.border.medium}`,
|
||||||
paddingBottom: 10,
|
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: theme.colors.text.link,
|
color: theme.colors.text.link,
|
||||||
}),
|
}),
|
||||||
|
|||||||
+18
-27
@@ -4,7 +4,7 @@ import { Draggable } from '@hello-pangea/dnd';
|
|||||||
import { DataFrame, DataLink, GrafanaTheme2 } from '@grafana/data';
|
import { DataFrame, DataLink, GrafanaTheme2 } from '@grafana/data';
|
||||||
|
|
||||||
import { useStyles2 } from '../../../themes';
|
import { useStyles2 } from '../../../themes';
|
||||||
import { isCompactUrl } from '../../../utils/dataLinks';
|
import { isCompactUrl } from '../../../utils';
|
||||||
import { FieldValidationMessage } from '../../Forms/FieldValidationMessage';
|
import { FieldValidationMessage } from '../../Forms/FieldValidationMessage';
|
||||||
import { Icon } from '../../Icon/Icon';
|
import { Icon } from '../../Icon/Icon';
|
||||||
import { IconButton } from '../../IconButton/IconButton';
|
import { IconButton } from '../../IconButton/IconButton';
|
||||||
@@ -39,10 +39,6 @@ export const DataLinksListItem = ({ link, onEdit, onRemove, index, itemKey }: Da
|
|||||||
{...provided.draggableProps}
|
{...provided.draggableProps}
|
||||||
key={index}
|
key={index}
|
||||||
>
|
>
|
||||||
<div className={cx(styles.dragHandle, styles.icons)} {...provided.dragHandleProps}>
|
|
||||||
<Icon name="draggabledots" size="lg" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.linkDetails}>
|
<div className={styles.linkDetails}>
|
||||||
<div className={cx(styles.url, !hasUrl && styles.notConfigured, isCompactExploreUrl && styles.errored)}>
|
<div className={cx(styles.url, !hasUrl && styles.notConfigured, isCompactExploreUrl && styles.errored)}>
|
||||||
{hasTitle ? title : 'Data link title not provided'}
|
{hasTitle ? title : 'Data link title not provided'}
|
||||||
@@ -59,9 +55,12 @@ export const DataLinksListItem = ({ link, onEdit, onRemove, index, itemKey }: Da
|
|||||||
</FieldValidationMessage>
|
</FieldValidationMessage>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className={styles.icons}>
|
||||||
<IconButton name="pen" onClick={onEdit} tooltip="Edit data link title" />
|
<IconButton name="pen" onClick={onEdit} className={styles.icon} tooltip="Edit data link" />
|
||||||
<IconButton name="times" onClick={onRemove} tooltip="Remove data link title" />
|
<IconButton name="trash-alt" onClick={onRemove} className={styles.icon} tooltip="Remove data link" />
|
||||||
|
<div className={styles.dragIcon} {...provided.dragHandleProps}>
|
||||||
|
<Icon name="draggabledots" size="lg" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -78,17 +77,16 @@ const getDataLinkListItemStyles = (theme: GrafanaTheme2) => {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
marginBottom: theme.spacing(2),
|
padding: '5px 0 5px 10px',
|
||||||
padding: '10px 0 0 10px',
|
borderRadius: theme.shape.radius.default,
|
||||||
'&:last-child': {
|
background: theme.colors.background.secondary,
|
||||||
marginBottom: 0,
|
gap: 8,
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
linkDetails: css({
|
linkDetails: css({
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
maxWidth: '80%',
|
maxWidth: `calc(100% - 100px)`,
|
||||||
}),
|
}),
|
||||||
errored: css({
|
errored: css({
|
||||||
color: theme.colors.error.text,
|
color: theme.colors.error.text,
|
||||||
@@ -118,20 +116,13 @@ const getDataLinkListItemStyles = (theme: GrafanaTheme2) => {
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
gap: 8,
|
gap: 8,
|
||||||
}),
|
}),
|
||||||
dragHandle: css({
|
dragIcon: css({
|
||||||
cursor: 'grab',
|
cursor: 'grab',
|
||||||
// create focus ring around the whole row when the drag handle is tab-focused
|
color: theme.colors.text.secondary,
|
||||||
// needs position: relative on the drag row to work correctly
|
margin: theme.spacing(0, 0.5),
|
||||||
'&:focus-visible&:after': {
|
}),
|
||||||
bottom: 0,
|
icon: css({
|
||||||
content: '""',
|
color: theme.colors.text.secondary,
|
||||||
left: 0,
|
|
||||||
position: 'absolute',
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
outline: `2px solid ${theme.colors.primary.main}`,
|
|
||||||
outlineOffset: '-2px',
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -10,12 +10,11 @@ import mdx from './Input.mdx';
|
|||||||
import { parseAccessory } from './storyUtils';
|
import { parseAccessory } from './storyUtils';
|
||||||
|
|
||||||
const prefixSuffixOpts = {
|
const prefixSuffixOpts = {
|
||||||
None: null,
|
$: 'Text',
|
||||||
Text: '$',
|
|
||||||
...getAvailableIcons().reduce<KeyValue<string>>((prev, c) => {
|
...getAvailableIcons().reduce<KeyValue<string>>((prev, c) => {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
[`Icon: ${c}`]: `icon-${c}`,
|
[`icon-${c}`]: `Icon: ${c}`,
|
||||||
};
|
};
|
||||||
}, {}),
|
}, {}),
|
||||||
};
|
};
|
||||||
@@ -43,20 +42,22 @@ const meta: Meta = {
|
|||||||
prefixVisible: {
|
prefixVisible: {
|
||||||
control: {
|
control: {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: prefixSuffixOpts,
|
labels: prefixSuffixOpts,
|
||||||
},
|
},
|
||||||
|
options: [null, ...Object.keys(prefixSuffixOpts)],
|
||||||
},
|
},
|
||||||
suffixVisible: {
|
suffixVisible: {
|
||||||
control: {
|
control: {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: prefixSuffixOpts,
|
labels: prefixSuffixOpts,
|
||||||
},
|
},
|
||||||
|
options: [null, ...Object.keys(prefixSuffixOpts)],
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
control: {
|
control: {
|
||||||
type: 'select',
|
type: 'select',
|
||||||
options: ['text', 'number', 'password'],
|
|
||||||
},
|
},
|
||||||
|
options: ['text', 'number', 'password'],
|
||||||
},
|
},
|
||||||
// validation: { name: 'Validation regex (will do a partial match if you do not anchor it)' },
|
// validation: { name: 'Validation regex (will do a partial match if you do not anchor it)' },
|
||||||
width: { control: { type: 'range', min: 10, max: 200, step: 10 } },
|
width: { control: { type: 'range', min: 10, max: 200, step: 10 } },
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export function getPageStyles(theme: GrafanaTheme2) {
|
|||||||
? {
|
? {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
minHeight: '100vh',
|
height: '100vh',
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ func dashboardGuardianResponse(err error) response.Response {
|
|||||||
// 404: notFoundError
|
// 404: notFoundError
|
||||||
// 500: internalServerError
|
// 500: internalServerError
|
||||||
func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response {
|
func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response {
|
||||||
ctx, span := hs.tracer.Start(c.Req.Context(), "httpserver.GetDashboard")
|
ctx, span := hs.tracer.Start(c.Req.Context(), "api.GetDashboard")
|
||||||
defer span.End()
|
defer span.End()
|
||||||
|
|
||||||
uid := web.Params(c.Req)[":uid"]
|
uid := web.Params(c.Req)[":uid"]
|
||||||
@@ -262,6 +262,9 @@ func (hs *HTTPServer) getUserLogin(ctx context.Context, userID int64) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) getDashboardHelper(ctx context.Context, orgID int64, id int64, uid string) (*dashboards.Dashboard, response.Response) {
|
func (hs *HTTPServer) getDashboardHelper(ctx context.Context, orgID int64, id int64, uid string) (*dashboards.Dashboard, response.Response) {
|
||||||
|
ctx, span := hs.tracer.Start(ctx, "api.getDashboardHelper")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
var query dashboards.GetDashboardQuery
|
var query dashboards.GetDashboardQuery
|
||||||
|
|
||||||
if len(uid) > 0 {
|
if len(uid) > 0 {
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ import (
|
|||||||
// Returns a file that is easy to check for changes
|
// Returns a file that is easy to check for changes
|
||||||
// Any changes to the file means we should refresh the frontend
|
// Any changes to the file means we should refresh the frontend
|
||||||
func (hs *HTTPServer) GetFrontendAssets(c *contextmodel.ReqContext) {
|
func (hs *HTTPServer) GetFrontendAssets(c *contextmodel.ReqContext) {
|
||||||
|
c, span := hs.injectSpan(c, "api.GetFrontendAssets")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
keys := map[string]any{}
|
keys := map[string]any{}
|
||||||
|
|
||||||
@@ -97,6 +100,9 @@ func (hs *HTTPServer) GetFrontendSettings(c *contextmodel.ReqContext) {
|
|||||||
//
|
//
|
||||||
//nolint:gocyclo
|
//nolint:gocyclo
|
||||||
func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.FrontendSettingsDTO, error) {
|
func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.FrontendSettingsDTO, error) {
|
||||||
|
c, span := hs.injectSpan(c, "api.getFrontendSettings")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
availablePlugins, err := hs.availablePlugins(c.Req.Context(), c.SignedInUser.GetOrgID())
|
availablePlugins, err := hs.availablePlugins(c.Req.Context(), c.SignedInUser.GetOrgID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -388,6 +394,9 @@ func getShortCommitHash(commitHash string, maxLength int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlugins AvailablePlugins) (map[string]plugins.DataSourceDTO, error) {
|
func (hs *HTTPServer) getFSDataSources(c *contextmodel.ReqContext, availablePlugins AvailablePlugins) (map[string]plugins.DataSourceDTO, error) {
|
||||||
|
c, span := hs.injectSpan(c, "api.getFSDataSources")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
orgDataSources := make([]*datasources.DataSource, 0)
|
orgDataSources := make([]*datasources.DataSource, 0)
|
||||||
if c.SignedInUser.GetOrgID() != 0 {
|
if c.SignedInUser.GetOrgID() != 0 {
|
||||||
query := datasources.GetDataSourcesQuery{OrgID: c.SignedInUser.GetOrgID(), DataSourceLimit: hs.Cfg.DataSourceLimit}
|
query := datasources.GetDataSourcesQuery{OrgID: c.SignedInUser.GetOrgID(), DataSourceLimit: hs.Cfg.DataSourceLimit}
|
||||||
@@ -620,6 +629,9 @@ func (ap AvailablePlugins) Get(pluginType plugins.Type, pluginID string) (*avail
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) availablePlugins(ctx context.Context, orgID int64) (AvailablePlugins, error) {
|
func (hs *HTTPServer) availablePlugins(ctx context.Context, orgID int64) (AvailablePlugins, error) {
|
||||||
|
ctx, span := hs.tracer.Start(ctx, "api.availablePlugins")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
ap := make(AvailablePlugins)
|
ap := make(AvailablePlugins)
|
||||||
|
|
||||||
pluginSettingMap, err := hs.pluginSettings(ctx, orgID)
|
pluginSettingMap, err := hs.pluginSettings(ctx, orgID)
|
||||||
@@ -665,6 +677,9 @@ func (hs *HTTPServer) availablePlugins(ctx context.Context, orgID int64) (Availa
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) pluginSettings(ctx context.Context, orgID int64) (map[string]*pluginsettings.InfoDTO, error) {
|
func (hs *HTTPServer) pluginSettings(ctx context.Context, orgID int64) (map[string]*pluginsettings.InfoDTO, error) {
|
||||||
|
ctx, span := hs.tracer.Start(ctx, "api.pluginSettings")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
pluginSettings := make(map[string]*pluginsettings.InfoDTO)
|
pluginSettings := make(map[string]*pluginsettings.InfoDTO)
|
||||||
|
|
||||||
// fill settings from database
|
// fill settings from database
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
"github.com/grafana/grafana/pkg/infra/remotecache"
|
"github.com/grafana/grafana/pkg/infra/remotecache"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"github.com/grafana/grafana/pkg/infra/usagestats"
|
"github.com/grafana/grafana/pkg/infra/usagestats"
|
||||||
"github.com/grafana/grafana/pkg/login/social/socialimpl"
|
"github.com/grafana/grafana/pkg/login/social/socialimpl"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
@@ -81,6 +82,7 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.F
|
|||||||
namespacer: request.GetNamespaceMapper(cfg),
|
namespacer: request.GetNamespaceMapper(cfg),
|
||||||
SocialService: socialimpl.ProvideService(cfg, features, &usagestats.UsageStatsMock{}, supportbundlestest.NewFakeBundleService(), remotecache.NewFakeCacheStorage(), nil, &ssosettingstests.MockService{}),
|
SocialService: socialimpl.ProvideService(cfg, features, &usagestats.UsageStatsMock{}, supportbundlestest.NewFakeBundleService(), remotecache.NewFakeCacheStorage(), nil, &ssosettingstests.MockService{}),
|
||||||
managedPluginsService: managedplugins.NewNoop(),
|
managedPluginsService: managedplugins.NewNoop(),
|
||||||
|
tracer: tracing.InitializeTracerForTest(),
|
||||||
}
|
}
|
||||||
|
|
||||||
m := web.New()
|
m := web.New()
|
||||||
|
|||||||
+50
-1
@@ -109,6 +109,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
|
"github.com/youmark/pkcs8"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HTTPServer struct {
|
type HTTPServer struct {
|
||||||
@@ -819,6 +820,10 @@ func (hs *HTTPServer) readCertificates() (*tls.Certificate, error) {
|
|||||||
return nil, fmt.Errorf(`cannot find SSL key_file at %q`, hs.Cfg.KeyFile)
|
return nil, fmt.Errorf(`cannot find SSL key_file at %q`, hs.Cfg.KeyFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hs.Cfg.CertPassword != "" {
|
||||||
|
return handleEncryptedCertificates(hs.Cfg)
|
||||||
|
}
|
||||||
|
// previous implementation
|
||||||
tlsCert, err := tls.LoadX509KeyPair(hs.Cfg.CertFile, hs.Cfg.KeyFile)
|
tlsCert, err := tls.LoadX509KeyPair(hs.Cfg.CertFile, hs.Cfg.KeyFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not load SSL certificate: %w", err)
|
return nil, fmt.Errorf("could not load SSL certificate: %w", err)
|
||||||
@@ -826,6 +831,50 @@ func (hs *HTTPServer) readCertificates() (*tls.Certificate, error) {
|
|||||||
return &tlsCert, nil
|
return &tlsCert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func handleEncryptedCertificates(cfg *setting.Cfg) (*tls.Certificate, error) {
|
||||||
|
certKeyFilePassword := cfg.CertPassword
|
||||||
|
certData, err := os.ReadFile(cfg.CertFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read certificate file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
keyData, err := os.ReadFile(cfg.KeyFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read private key file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle encrypted private key
|
||||||
|
keyPemBlock, _ := pem.Decode(keyData)
|
||||||
|
|
||||||
|
var keyBytes []byte
|
||||||
|
// Process the PKCS-encrypted PEM block.
|
||||||
|
if strings.Contains(keyPemBlock.Type, "ENCRYPTED") {
|
||||||
|
// The pkcs8 package only handles the PKCS #5 v2.0 scheme.
|
||||||
|
decrypted, err := pkcs8.ParsePKCS8PrivateKey(keyPemBlock.Bytes, []byte(certKeyFilePassword))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing PKCS8 Private key: %w", err)
|
||||||
|
}
|
||||||
|
keyBytes, err = x509.MarshalPKCS8PrivateKey(decrypted)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error marshaling PKCS8 Private key: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("password provided but Private key is not encrypted or not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
var encodedKey bytes.Buffer
|
||||||
|
err = pem.Encode(&encodedKey, &pem.Block{Type: keyPemBlock.Type, Bytes: keyBytes})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error encoding pem file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := tls.X509KeyPair(certData, encodedKey.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse X509 key pair: %w", err)
|
||||||
|
}
|
||||||
|
return &cert, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) configureTLS() error {
|
func (hs *HTTPServer) configureTLS() error {
|
||||||
tlsCerts, err := hs.tlsCertificates()
|
tlsCerts, err := hs.tlsCertificates()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -869,7 +918,7 @@ func (hs *HTTPServer) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, er
|
|||||||
return tlsCerts, nil
|
return tlsCerts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// fsnotify module can be used to detect file changes and based on the event certs can be reloaded
|
// WatchAndUpdateCerts fsnotify module can be used to detect file changes and based on the event certs can be reloaded
|
||||||
// since it adds a direct dependency for the optional feature. So that is the reason periodic watching
|
// since it adds a direct dependency for the optional feature. So that is the reason periodic watching
|
||||||
// of cert files is chosen. If fsnotify is added as direct dependency in future, then the implementation
|
// of cert files is chosen. If fsnotify is added as direct dependency in future, then the implementation
|
||||||
// can be revisited to align to fsnotify.
|
// can be revisited to align to fsnotify.
|
||||||
|
|||||||
+124
-2
@@ -1,11 +1,12 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHTTPServer_MetricsBasicAuth(t *testing.T) {
|
func TestHTTPServer_MetricsBasicAuth(t *testing.T) {
|
||||||
@@ -37,3 +38,124 @@ func TestHTTPServer_readCertificates(t *testing.T) {
|
|||||||
assert.NotNil(t, err)
|
assert.NotNil(t, err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHTTPServer_readEncryptedCertificates(t *testing.T) {
|
||||||
|
t.Run("readCertificates should return certificate if configuration is correct", func(t *testing.T) {
|
||||||
|
cfg, cleanUpFunc := getHttpServerCfg(t)
|
||||||
|
defer cleanUpFunc()
|
||||||
|
|
||||||
|
ts := &HTTPServer{
|
||||||
|
Cfg: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := ts.readCertificates()
|
||||||
|
require.Nil(t, err)
|
||||||
|
require.NotNil(t, c)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("readCertificates should return error if the password provided is not the correct one", func(t *testing.T) {
|
||||||
|
cfg, cleanUpFunc := getHttpServerCfg(t)
|
||||||
|
defer cleanUpFunc()
|
||||||
|
// change for a wrong password - 32char for consistency
|
||||||
|
cfg.CertPassword = "somethingThatIsNotTheCorrectPass"
|
||||||
|
|
||||||
|
ts := &HTTPServer{
|
||||||
|
Cfg: cfg,
|
||||||
|
}
|
||||||
|
|
||||||
|
c, err := ts.readCertificates()
|
||||||
|
require.Nil(t, c)
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Equal(t, err.Error(), "error parsing PKCS8 Private key: pkcs8: incorrect password")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns Cfg and cleanup function for the created files
|
||||||
|
func getHttpServerCfg(t *testing.T) (*setting.Cfg, func()) {
|
||||||
|
// create cert files
|
||||||
|
cert, err := os.CreateTemp("", "certWithPass*.crt")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = cert.Write(certWithPass)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
privateKey, err := os.CreateTemp("", "privateKey*.key")
|
||||||
|
require.NoError(t, err)
|
||||||
|
_, err = privateKey.Write(privateKeyWithPass)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cfg := setting.NewCfg()
|
||||||
|
cfg.CertPassword = password
|
||||||
|
cfg.CertFile = cert.Name()
|
||||||
|
cfg.KeyFile = privateKey.Name()
|
||||||
|
cfg.Protocol = "https"
|
||||||
|
|
||||||
|
cleanupFunc := func() {
|
||||||
|
_ = os.Remove(cert.Name())
|
||||||
|
_ = os.Remove(privateKey.Name())
|
||||||
|
}
|
||||||
|
|
||||||
|
return cfg, cleanupFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Certificates encrypted with password used for testing. These are valid until Aug 1st 2027.
|
||||||
|
* To generate new ones, use this commands:
|
||||||
|
*
|
||||||
|
* # Generate RSA private key with a passphrase '12345678901234567890123456789012'
|
||||||
|
* sudo openssl genrsa -aes256 -passout pass:12345678901234567890123456789012 -out ./grafana_pass.key 2048
|
||||||
|
* # Create a new Certificate Signing Request (CSR) using the private key passing passphrase '12345678901234567890123456789012'
|
||||||
|
* sudo openssl req -new -nodes -sha256 -key ./grafana_pass.key -subj '/CN=testCertWithPass/C=us' -passin pass:12345678901234567890123456789012 -out ./grafana_pass.csr
|
||||||
|
* # Sign the CSR using the private key to create a self-signed certificate valid for 365 days
|
||||||
|
* sudo openssl x509 -req -days 1095 -in ./grafana_pass.csr -signkey ./grafana_pass.key -passin pass:12345678901234567890123456789012 -out ./grafana_pass.crt
|
||||||
|
*/
|
||||||
|
var certWithPass = []byte(`-----BEGIN CERTIFICATE-----
|
||||||
|
MIIC1zCCAb8CFGUb9G3+Dl7bTJgCsV0HatdD6jnkMA0GCSqGSIb3DQEBCwUAMCgx
|
||||||
|
GTAXBgNVBAMMEHRlc3RDZXJ0V2l0aFBhc3MxCzAJBgNVBAYTAnVzMB4XDTI0MDgw
|
||||||
|
MTE4MzM0OFoXDTI3MDgwMTE4MzM0OFowKDEZMBcGA1UEAwwQdGVzdENlcnRXaXRo
|
||||||
|
UGFzczELMAkGA1UEBhMCdXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||||
|
AQCKnZHWYZLgfpV2MqhTHxpONwQ6dUWWwAl3sQaLV2VH6e0qBhCaO4gCKQbv3KeH
|
||||||
|
4sXmdYG4fKJ+SnwGhljfW4anQjb/puVSX8E4EXwf81DBUKbUGs5GvIx6oIx2HkoO
|
||||||
|
BoKBNgsk8K/Eq4XcVUo8PfxbsJzoCyxcrjelV4UDgxpwDCTaewmiIUb+V/JvQi65
|
||||||
|
J1EWWofghKkNwhZ0Qyh6I9O8N7ZbkEUSbATcZ32AoDhpzhbVXQkNhJJV5SSa2zaA
|
||||||
|
Bv50cni9Te4PEYq97xUkq2KaD3c+Ie1VrAAmJVCgcUylG1YeZUohyaLbY7DG/PaW
|
||||||
|
ZPu6OqKddfH1UxUG0xzRjbmJAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAEZXVWWV
|
||||||
|
GdaSUuBlc9Rd6DvSQSBYzBm5zfoQlw1IQT93tI4SVD2U04RPfxUdCh6QxsssitRn
|
||||||
|
tz2x3EKFBQ3x0jYk+JHxBLdTWAhdWrhFB+beUuOUQ5++cBDTHvpyoROAg/cIz4Fg
|
||||||
|
PvdhneOlQBe7Vh1Uv4ez+H7U1MtgUAt2LYhb5hundhUpH/WCsn1mlehyhrbDBzPc
|
||||||
|
f9JeTlZbe6wyvS/26qGPSCgP0KNvltR0Cjf2AV2gjX/7+BUr9qFBRjs4+jZkIRkP
|
||||||
|
fsYk656OSlFMbYlst1ktnBrmBE7AOHdW/WRynfIFQACNkwnrnPO1u8ZRSUzVlg/2
|
||||||
|
lzZlmPUgKBVA0kA=
|
||||||
|
-----END CERTIFICATE-----`)
|
||||||
|
|
||||||
|
var privateKeyWithPass = []byte(`-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END ENCRYPTED PRIVATE KEY-----
|
||||||
|
`)
|
||||||
|
var password = "12345678901234567890123456789012"
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexViewData, error) {
|
func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexViewData, error) {
|
||||||
|
c, span := hs.injectSpan(c, "api.setIndexViewData")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
settings, err := hs.getFrontendSettings(c)
|
settings, err := hs.getFrontendSettings(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -215,6 +218,9 @@ func hashUserIdentifier(identifier string, secret string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (hs *HTTPServer) Index(c *contextmodel.ReqContext) {
|
func (hs *HTTPServer) Index(c *contextmodel.ReqContext) {
|
||||||
|
c, span := hs.injectSpan(c, "api.Index")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
data, err := hs.setIndexViewData(c)
|
data, err := hs.setIndexViewData(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Handle(hs.Cfg, http.StatusInternalServerError, "Failed to get settings", err)
|
c.Handle(hs.Cfg, http.StatusInternalServerError, "Failed to get settings", err)
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ import (
|
|||||||
// 422: unprocessableEntityError
|
// 422: unprocessableEntityError
|
||||||
// 500: internalServerError
|
// 500: internalServerError
|
||||||
func (hs *HTTPServer) Search(c *contextmodel.ReqContext) response.Response {
|
func (hs *HTTPServer) Search(c *contextmodel.ReqContext) response.Response {
|
||||||
|
c, span := hs.injectSpan(c, "api.Search")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
query := c.Query("query")
|
query := c.Query("query")
|
||||||
tags := c.QueryStrings("tag")
|
tags := c.QueryStrings("tag")
|
||||||
starred := c.Query("starred")
|
starred := c.Query("starred")
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/login"
|
"github.com/grafana/grafana/pkg/services/login"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (hs *HTTPServer) GetRedirectURL(c *contextmodel.ReqContext) string {
|
func (hs *HTTPServer) GetRedirectURL(c *contextmodel.ReqContext) string {
|
||||||
@@ -63,3 +64,9 @@ func ValidateAndNormalizeEmail(email string) (string, error) {
|
|||||||
|
|
||||||
return e.Address, nil
|
return e.Address, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hs *HTTPServer) injectSpan(c *contextmodel.ReqContext, name string) (*contextmodel.ReqContext, trace.Span) {
|
||||||
|
ctx, span := hs.tracer.Start(c.Req.Context(), name)
|
||||||
|
c.Req = c.Req.WithContext(ctx)
|
||||||
|
return c, span
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@@ -18,12 +19,13 @@ type ResourceInfo struct {
|
|||||||
kind string
|
kind string
|
||||||
newObj func() runtime.Object
|
newObj func() runtime.Object
|
||||||
newList func() runtime.Object
|
newList func() runtime.Object
|
||||||
|
columns utils.TableColumns
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewResourceInfo(group, version, resourceName, singularName, kind string,
|
func NewResourceInfo(group, version, resourceName, singularName, kind string,
|
||||||
newObj func() runtime.Object, newList func() runtime.Object) ResourceInfo {
|
newObj func() runtime.Object, newList func() runtime.Object, columns utils.TableColumns) ResourceInfo {
|
||||||
shortName := "" // an optional alias helpful in kubectl eg ("sa" for serviceaccounts)
|
shortName := "" // an optional alias helpful in kubectl eg ("sa" for serviceaccounts)
|
||||||
return ResourceInfo{group, version, resourceName, singularName, shortName, kind, newObj, newList}
|
return ResourceInfo{group, version, resourceName, singularName, shortName, kind, newObj, newList, columns}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (info *ResourceInfo) WithGroupAndShortName(group string, shortName string) ResourceInfo {
|
func (info *ResourceInfo) WithGroupAndShortName(group string, shortName string) ResourceInfo {
|
||||||
@@ -36,6 +38,7 @@ func (info *ResourceInfo) WithGroupAndShortName(group string, shortName string)
|
|||||||
shortName: shortName,
|
shortName: shortName,
|
||||||
newObj: info.newObj,
|
newObj: info.newObj,
|
||||||
newList: info.newList,
|
newList: info.newList,
|
||||||
|
columns: info.columns,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +116,10 @@ func (info *ResourceInfo) NewListFunc() runtime.Object {
|
|||||||
return info.newList()
|
return info.newList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (info *ResourceInfo) TableConverter() utils.TableConvertor {
|
||||||
|
return utils.NewTableConverter(info.GroupResource(), info.columns)
|
||||||
|
}
|
||||||
|
|
||||||
func (info *ResourceInfo) NewNotFound(name string) *errors.StatusError {
|
func (info *ResourceInfo) NewNotFound(name string) *errors.StatusError {
|
||||||
return errors.NewNotFound(info.SingularGroupResource(), name)
|
return errors.NewNotFound(info.SingularGroupResource(), name)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"fmt"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"time"
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -17,18 +21,78 @@ var UserResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
"users", "user", "User",
|
"users", "user", "User",
|
||||||
func() runtime.Object { return &User{} },
|
func() runtime.Object { return &User{} },
|
||||||
func() runtime.Object { return &UserList{} },
|
func() runtime.Object { return &UserList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Login", Type: "string", Format: "string", Description: "The user login"},
|
||||||
|
{Name: "Email", Type: "string", Format: "string", Description: "The user email"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
u, ok := obj.(*User)
|
||||||
|
if ok {
|
||||||
|
return []interface{}{
|
||||||
|
u.Name,
|
||||||
|
u.Spec.Login,
|
||||||
|
u.Spec.Email,
|
||||||
|
u.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("expected user")
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
var TeamResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
var TeamResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
||||||
"teams", "team", "Team",
|
"teams", "team", "Team",
|
||||||
func() runtime.Object { return &Team{} },
|
func() runtime.Object { return &Team{} },
|
||||||
func() runtime.Object { return &TeamList{} },
|
func() runtime.Object { return &TeamList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Title", Type: "string", Format: "string", Description: "The team name"},
|
||||||
|
{Name: "Email", Type: "string", Format: "string", Description: "team email"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
m, ok := obj.(*Team)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("expected team")
|
||||||
|
}
|
||||||
|
return []interface{}{
|
||||||
|
m.Name,
|
||||||
|
m.Spec.Title,
|
||||||
|
m.Spec.Email,
|
||||||
|
m.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
var ServiceAccountResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
var ServiceAccountResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
||||||
"serviceaccounts", "serviceaccount", "ServiceAccount",
|
"serviceaccounts", "serviceaccount", "ServiceAccount",
|
||||||
func() runtime.Object { return &ServiceAccount{} },
|
func() runtime.Object { return &ServiceAccount{} },
|
||||||
func() runtime.Object { return &ServiceAccountList{} },
|
func() runtime.Object { return &ServiceAccountList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Account", Type: "string", Format: "string", Description: "The service account email"},
|
||||||
|
{Name: "Email", Type: "string", Format: "string", Description: "The user email"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
u, ok := obj.(*ServiceAccount)
|
||||||
|
if ok {
|
||||||
|
return []interface{}{
|
||||||
|
u.Name,
|
||||||
|
u.Spec.Name,
|
||||||
|
u.Spec.Email,
|
||||||
|
u.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("expected service account")
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
+52
-51
@@ -11,70 +11,74 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apiserver/pkg/endpoints/request"
|
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type TableColumns struct {
|
||||||
|
Definition []metav1.TableColumnDefinition
|
||||||
|
Reader func(obj any) ([]interface{}, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TableConvertor interface {
|
||||||
|
ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error)
|
||||||
|
}
|
||||||
|
|
||||||
// Based on https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/registry/rest/table.go
|
// Based on https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/registry/rest/table.go
|
||||||
type customTableConvertor struct {
|
type customTableConvertor struct {
|
||||||
gr schema.GroupResource
|
gr schema.GroupResource
|
||||||
columns []metav1.TableColumnDefinition
|
columns TableColumns
|
||||||
reader func(obj any) ([]interface{}, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTableConverter(gr schema.GroupResource, columns []metav1.TableColumnDefinition, reader func(obj any) ([]interface{}, error)) rest.TableConvertor {
|
func NewTableConverter(gr schema.GroupResource, columns TableColumns) TableConvertor {
|
||||||
converter := customTableConvertor{
|
if columns.Reader == nil {
|
||||||
gr: gr,
|
columns = TableColumns{
|
||||||
columns: columns,
|
Definition: []metav1.TableColumnDefinition{
|
||||||
reader: reader,
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
v, err := meta.Accessor(obj)
|
||||||
|
if err == nil && v != nil {
|
||||||
|
return []interface{}{
|
||||||
|
v.GetName(),
|
||||||
|
v.GetCreationTimestamp().UTC().Format(time.RFC3339),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
r := reflect.ValueOf(obj).Elem()
|
||||||
|
n := r.FieldByName("Name").String()
|
||||||
|
if n != "" {
|
||||||
|
return []interface{}{
|
||||||
|
n,
|
||||||
|
"",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return []interface{}{
|
||||||
|
fmt.Sprintf("%v", obj),
|
||||||
|
"",
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the description on standard columns with the global values
|
// Replace the description on standard columns with the global values
|
||||||
for idx, column := range converter.columns {
|
for idx, column := range columns.Definition {
|
||||||
if column.Description == "" {
|
if column.Description == "" {
|
||||||
switch column.Name {
|
switch column.Name {
|
||||||
case "Name":
|
case "Name":
|
||||||
converter.columns[idx].Description = swaggerMetadataDescriptions["name"]
|
columns.Definition[idx].Description = swaggerMetadataDescriptions["name"]
|
||||||
case "Created At":
|
case "Created At":
|
||||||
converter.columns[idx].Description = swaggerMetadataDescriptions["creationTimestamp"]
|
columns.Definition[idx].Description = swaggerMetadataDescriptions["creationTimestamp"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return converter
|
return customTableConvertor{
|
||||||
|
gr: gr,
|
||||||
|
columns: columns,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDefaultTableConverter(gr schema.GroupResource) rest.TableConvertor {
|
var _ TableConvertor = &customTableConvertor{}
|
||||||
return NewTableConverter(gr,
|
|
||||||
[]metav1.TableColumnDefinition{
|
|
||||||
{Name: "Name", Type: "string", Format: "name"},
|
|
||||||
{Name: "Created At", Type: "date"},
|
|
||||||
},
|
|
||||||
func(obj any) ([]interface{}, error) {
|
|
||||||
v, err := meta.Accessor(obj)
|
|
||||||
if err == nil && v != nil {
|
|
||||||
return []interface{}{
|
|
||||||
v.GetName(),
|
|
||||||
v.GetCreationTimestamp().UTC().Format(time.RFC3339),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r := reflect.ValueOf(obj).Elem()
|
|
||||||
n := r.FieldByName("Name").String()
|
|
||||||
if n != "" {
|
|
||||||
return []interface{}{
|
|
||||||
n,
|
|
||||||
"",
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return []interface{}{
|
|
||||||
fmt.Sprintf("%v", obj),
|
|
||||||
"",
|
|
||||||
}, nil
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ rest.TableConvertor = &customTableConvertor{}
|
|
||||||
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
|
var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc()
|
||||||
|
|
||||||
func (c customTableConvertor) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
func (c customTableConvertor) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||||
@@ -85,12 +89,9 @@ func (c customTableConvertor) ConvertToTable(ctx context.Context, object runtime
|
|||||||
table = &metav1.Table{}
|
table = &metav1.Table{}
|
||||||
}
|
}
|
||||||
fn := func(obj runtime.Object) error {
|
fn := func(obj runtime.Object) error {
|
||||||
cells, err := c.reader(obj)
|
cells, err := c.columns.Reader(obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resource := c.gr
|
resource := c.gr
|
||||||
if info, ok := request.RequestInfoFrom(ctx); ok {
|
|
||||||
resource = schema.GroupResource{Group: info.APIGroup, Resource: info.Resource}
|
|
||||||
}
|
|
||||||
return errNotAcceptable{resource: resource}
|
return errNotAcceptable{resource: resource}
|
||||||
}
|
}
|
||||||
table.Rows = append(table.Rows, metav1.TableRow{
|
table.Rows = append(table.Rows, metav1.TableRow{
|
||||||
@@ -119,7 +120,7 @@ func (c customTableConvertor) ConvertToTable(ctx context.Context, object runtime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if opt, ok := tableOptions.(*metav1.TableOptions); !ok || !opt.NoHeaders {
|
if opt, ok := tableOptions.(*metav1.TableOptions); !ok || !opt.NoHeaders {
|
||||||
table.ColumnDefinitions = c.columns
|
table.ColumnDefinitions = c.columns.Definition
|
||||||
}
|
}
|
||||||
return table, nil
|
return table, nil
|
||||||
}
|
}
|
||||||
+20
-19
@@ -7,33 +7,34 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTableConverter(t *testing.T) {
|
func TestTableConverter(t *testing.T) {
|
||||||
// dummy converter
|
// dummy converter
|
||||||
converter := utils.NewTableConverter(
|
converter := utils.NewTableConverter(
|
||||||
schema.GroupResource{Group: "x", Resource: "y"},
|
schema.GroupResource{Group: "x", Resource: "y"},
|
||||||
[]metav1.TableColumnDefinition{
|
utils.TableColumns{
|
||||||
{Name: "Name", Type: "string", Format: "name"},
|
Definition: []metav1.TableColumnDefinition{
|
||||||
{Name: "Dummy", Type: "string", Format: "string", Description: "Something here"},
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
{Name: "Created At", Type: "date"},
|
{Name: "Dummy", Type: "string", Format: "string", Description: "Something here"},
|
||||||
},
|
{Name: "Created At", Type: "date"},
|
||||||
func(obj any) ([]interface{}, error) {
|
},
|
||||||
m, ok := obj.(*metav1.APIGroup)
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
if !ok {
|
m, ok := obj.(*metav1.APIGroup)
|
||||||
return nil, fmt.Errorf("expected status")
|
if !ok {
|
||||||
}
|
return nil, fmt.Errorf("expected status")
|
||||||
ts := metav1.NewTime(time.UnixMilli(10000000))
|
}
|
||||||
return []interface{}{
|
ts := metav1.NewTime(time.UnixMilli(10000000))
|
||||||
m.Name,
|
return []interface{}{
|
||||||
"dummy",
|
m.Name,
|
||||||
ts.Time.UTC().Format(time.RFC3339),
|
"dummy",
|
||||||
}, nil
|
ts.Time.UTC().Format(time.RFC3339),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -97,7 +98,7 @@ func TestTableConverter(t *testing.T) {
|
|||||||
|
|
||||||
// Default table converter
|
// Default table converter
|
||||||
// Convert a single table
|
// Convert a single table
|
||||||
converter = utils.NewDefaultTableConverter(schema.GroupResource{Group: "x", Resource: "y"})
|
converter = utils.NewTableConverter(schema.GroupResource{Group: "x", Resource: "y"}, utils.TableColumns{})
|
||||||
table, err = converter.ConvertToTable(context.Background(), &metav1.APIGroup{
|
table, err = converter.ConvertToTable(context.Background(), &metav1.APIGroup{
|
||||||
Name: "hello",
|
Name: "hello",
|
||||||
}, nil)
|
}, nil)
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import "encoding/json"
|
import (
|
||||||
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
// Integration defines model for Integration.
|
// Integration defines model for Integration.
|
||||||
// +k8s:openapi-gen=true
|
// +k8s:openapi-gen=true
|
||||||
type Integration struct {
|
type Integration struct {
|
||||||
DisableResolveMessage *bool `json:"disableResolveMessage,omitempty"`
|
DisableResolveMessage *bool `json:"disableResolveMessage,omitempty"`
|
||||||
// +mapType=atomic
|
// +mapType=atomic
|
||||||
SecureFields map[string]bool `json:"SecureFields,omitempty"`
|
SecureFields map[string]bool `json:"secureFields,omitempty"`
|
||||||
// +listType=atomic
|
Settings common.Unstructured `json:"settings"`
|
||||||
Settings json.RawMessage `json:"settings"`
|
Type string `json:"type"`
|
||||||
Type string `json:"type"`
|
Uid *string `json:"uid,omitempty"`
|
||||||
Uid *string `json:"uid,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReceiverSpec defines model for Spec.
|
// ReceiverSpec defines model for Spec.
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1"
|
scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,11 +29,44 @@ var (
|
|||||||
"timeintervals", "timeinterval", "TimeInterval",
|
"timeintervals", "timeinterval", "TimeInterval",
|
||||||
func() runtime.Object { return &TimeInterval{} },
|
func() runtime.Object { return &TimeInterval{} },
|
||||||
func() runtime.Object { return &TimeIntervalList{} },
|
func() runtime.Object { return &TimeIntervalList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
// {Name: "Intervals", Type: "string", Format: "string", Description: "The display name"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
r, ok := obj.(*TimeInterval)
|
||||||
|
if ok {
|
||||||
|
return []interface{}{
|
||||||
|
r.Name,
|
||||||
|
// r.Spec, //TODO implement formatting for Spec, same as UI?
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("expected resource or info")
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
ReceiverResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
ReceiverResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
||||||
"receivers", "receiver", "Receiver",
|
"receivers", "receiver", "Receiver",
|
||||||
func() runtime.Object { return &Receiver{} },
|
func() runtime.Object { return &Receiver{} },
|
||||||
func() runtime.Object { return &ReceiverList{} },
|
func() runtime.Object { return &ReceiverList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Title", Type: "string", Format: "string", Description: "The receiver name"}, // TODO: Add integration types.
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
r, ok := obj.(*Receiver)
|
||||||
|
if ok {
|
||||||
|
return []interface{}{
|
||||||
|
r.Name,
|
||||||
|
r.Spec.Title,
|
||||||
|
// r.Spec, //TODO implement formatting for Spec, same as UI?
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("expected resource or info")
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
// SchemeGroupVersion is group version used to register these objects
|
// SchemeGroupVersion is group version used to register these objects
|
||||||
SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION}
|
SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION}
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
json "encoding/json"
|
|
||||||
|
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -28,11 +26,7 @@ func (in *Integration) DeepCopyInto(out *Integration) {
|
|||||||
(*out)[key] = val
|
(*out)[key] = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if in.Settings != nil {
|
in.Settings.DeepCopyInto(&out.Settings)
|
||||||
in, out := &in.Settings, &out.Settings
|
|
||||||
*out = make(json.RawMessage, len(*in))
|
|
||||||
copy(*out, *in)
|
|
||||||
}
|
|
||||||
if in.Uid != nil {
|
if in.Uid != nil {
|
||||||
in, out := &in.Uid, &out.Uid
|
in, out := &in.Uid, &out.Uid
|
||||||
*out = new(string)
|
*out = new(string)
|
||||||
|
|||||||
@@ -48,28 +48,12 @@ func schema_pkg_apis_alerting_notifications_v0alpha1_Integration(ref common.Refe
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Type: []string{"object"},
|
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"),
|
||||||
AdditionalProperties: &spec.SchemaOrBool{
|
|
||||||
Allows: true,
|
|
||||||
Schema: &spec.Schema{
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Default: false,
|
|
||||||
Type: []string{"boolean"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
VendorExtensible: spec.VendorExtensible{
|
|
||||||
Extensions: spec.Extensions{
|
|
||||||
"x-kubernetes-list-type": "atomic",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Type: []string{"string"},
|
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"),
|
||||||
Format: "byte",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"type": {
|
"type": {
|
||||||
@@ -89,6 +73,8 @@ func schema_pkg_apis_alerting_notifications_v0alpha1_Integration(ref common.Refe
|
|||||||
Required: []string{"settings", "type"},
|
Required: []string{"settings", "type"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Dependencies: []string{
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"fmt"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"time"
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -17,6 +21,26 @@ var DashboardResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
"dashboards", "dashboard", "Dashboard",
|
"dashboards", "dashboard", "Dashboard",
|
||||||
func() runtime.Object { return &Dashboard{} },
|
func() runtime.Object { return &Dashboard{} },
|
||||||
func() runtime.Object { return &DashboardList{} },
|
func() runtime.Object { return &DashboardList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Title", Type: "string", Format: "string", Description: "The dashboard name"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
dash, ok := obj.(*Dashboard)
|
||||||
|
if ok {
|
||||||
|
if dash != nil {
|
||||||
|
return []interface{}{
|
||||||
|
dash.Name,
|
||||||
|
dash.Spec.GetNestedString("title"),
|
||||||
|
dash.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("expected dashboard or summary")
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"fmt"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"time"
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -17,6 +21,24 @@ var DashboardSnapshotResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
"dashboardsnapshots", "dashboardsnapshot", "DashboardSnapshot",
|
"dashboardsnapshots", "dashboardsnapshot", "DashboardSnapshot",
|
||||||
func() runtime.Object { return &DashboardSnapshot{} },
|
func() runtime.Object { return &DashboardSnapshot{} },
|
||||||
func() runtime.Object { return &DashboardSnapshotList{} },
|
func() runtime.Object { return &DashboardSnapshotList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Title", Type: "string", Format: "string", Description: "The snapshot name"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
m, ok := obj.(*DashboardSnapshot)
|
||||||
|
if ok {
|
||||||
|
return []interface{}{
|
||||||
|
m.Name,
|
||||||
|
m.Spec.Title,
|
||||||
|
m.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("expected snapshot")
|
||||||
|
},
|
||||||
|
}, // default table converter
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -15,4 +19,24 @@ var GenericConnectionResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
"connections", "connection", "DataSourceConnection",
|
"connections", "connection", "DataSourceConnection",
|
||||||
func() runtime.Object { return &DataSourceConnection{} },
|
func() runtime.Object { return &DataSourceConnection{} },
|
||||||
func() runtime.Object { return &DataSourceConnectionList{} },
|
func() runtime.Object { return &DataSourceConnectionList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Title", Type: "string", Format: "string", Description: "The datasource title"},
|
||||||
|
{Name: "APIVersion", Type: "string", Format: "string", Description: "API Version"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
m, ok := obj.(*DataSourceConnection)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("expected connection")
|
||||||
|
}
|
||||||
|
return []interface{}{
|
||||||
|
m.Name,
|
||||||
|
m.Title,
|
||||||
|
m.APIVersion,
|
||||||
|
m.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
"fmt"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -18,6 +21,24 @@ var FeatureResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
"features", "feature", "Feature",
|
"features", "feature", "Feature",
|
||||||
func() runtime.Object { return &Feature{} },
|
func() runtime.Object { return &Feature{} },
|
||||||
func() runtime.Object { return &FeatureList{} },
|
func() runtime.Object { return &FeatureList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Stage", Type: "string", Format: "string", Description: "Where is the flag in the dev cycle"},
|
||||||
|
{Name: "Owner", Type: "string", Format: "string", Description: "Which team owns the feature"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
r, ok := obj.(*Feature)
|
||||||
|
if ok {
|
||||||
|
return []interface{}{
|
||||||
|
r.Name,
|
||||||
|
r.Spec.Stage,
|
||||||
|
r.Spec.Owner,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("expected resource or info")
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
// TogglesResourceInfo represents the actual configuration
|
// TogglesResourceInfo represents the actual configuration
|
||||||
@@ -25,6 +46,7 @@ var TogglesResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
"featuretoggles", "featuretoggle", "FeatureToggles",
|
"featuretoggles", "featuretoggle", "FeatureToggles",
|
||||||
func() runtime.Object { return &FeatureToggles{} },
|
func() runtime.Object { return &FeatureToggles{} },
|
||||||
func() runtime.Object { return &FeatureTogglesList{} },
|
func() runtime.Object { return &FeatureTogglesList{} },
|
||||||
|
utils.TableColumns{}, // default table converter
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"fmt"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -18,6 +21,25 @@ var FolderResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
RESOURCE, "folder", "Folder",
|
RESOURCE, "folder", "Folder",
|
||||||
func() runtime.Object { return &Folder{} },
|
func() runtime.Object { return &Folder{} },
|
||||||
func() runtime.Object { return &FolderList{} },
|
func() runtime.Object { return &FolderList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Title", Type: "string", Format: "string", Description: "The display name"},
|
||||||
|
{Name: "Parent", Type: "string", Format: "string", Description: "Parent folder UID"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
r, ok := obj.(*Folder)
|
||||||
|
if ok {
|
||||||
|
accessor, _ := utils.MetaAccessor(r)
|
||||||
|
return []interface{}{
|
||||||
|
r.Name,
|
||||||
|
r.Spec.Title,
|
||||||
|
accessor.GetFolder(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("expected folder")
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -18,6 +19,7 @@ var QueryTemplateResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
"querytemplates", "querytemplate", "QueryTemplate",
|
"querytemplates", "querytemplate", "QueryTemplate",
|
||||||
func() runtime.Object { return &QueryTemplate{} },
|
func() runtime.Object { return &QueryTemplate{} },
|
||||||
func() runtime.Object { return &QueryTemplateList{} },
|
func() runtime.Object { return &QueryTemplateList{} },
|
||||||
|
utils.TableColumns{}, // default table converter
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"fmt"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"time"
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -19,6 +23,26 @@ var PlaylistResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
RESOURCE, "playlist", "Playlist",
|
RESOURCE, "playlist", "Playlist",
|
||||||
func() runtime.Object { return &Playlist{} },
|
func() runtime.Object { return &Playlist{} },
|
||||||
func() runtime.Object { return &PlaylistList{} },
|
func() runtime.Object { return &PlaylistList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Title", Type: "string", Format: "string", Description: "The playlist name"},
|
||||||
|
{Name: "Interval", Type: "string", Format: "string", Description: "How often the playlist will update"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
m, ok := obj.(*Playlist)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("expected playlist")
|
||||||
|
}
|
||||||
|
return []interface{}{
|
||||||
|
m.Name,
|
||||||
|
m.Spec.Title,
|
||||||
|
m.Spec.Interval,
|
||||||
|
m.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -17,12 +18,14 @@ var DataSourceApiServerResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
"datasourceapiservers", "datasourceapiserver", "DataSourceApiServer",
|
"datasourceapiservers", "datasourceapiserver", "DataSourceApiServer",
|
||||||
func() runtime.Object { return &DataSourceApiServer{} },
|
func() runtime.Object { return &DataSourceApiServer{} },
|
||||||
func() runtime.Object { return &DataSourceApiServerList{} },
|
func() runtime.Object { return &DataSourceApiServerList{} },
|
||||||
|
utils.TableColumns{}, // default table converter
|
||||||
)
|
)
|
||||||
|
|
||||||
var QueryTypeDefinitionResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
var QueryTypeDefinitionResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
||||||
"querytypes", "querytype", "QueryTypeDefinition",
|
"querytypes", "querytype", "QueryTypeDefinition",
|
||||||
func() runtime.Object { return &QueryTypeDefinition{} },
|
func() runtime.Object { return &QueryTypeDefinition{} },
|
||||||
func() runtime.Object { return &QueryTypeDefinitionList{} },
|
func() runtime.Object { return &QueryTypeDefinitionList{} },
|
||||||
|
utils.TableColumns{}, // default table converter
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"fmt"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"time"
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -17,18 +21,84 @@ var ScopeResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
"scopes", "scope", "Scope",
|
"scopes", "scope", "Scope",
|
||||||
func() runtime.Object { return &Scope{} },
|
func() runtime.Object { return &Scope{} },
|
||||||
func() runtime.Object { return &ScopeList{} },
|
func() runtime.Object { return &ScopeList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
{Name: "Title", Type: "string"},
|
||||||
|
{Name: "Filters", Type: "array"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
m, ok := obj.(*Scope)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("expected scope")
|
||||||
|
}
|
||||||
|
return []interface{}{
|
||||||
|
m.Name,
|
||||||
|
m.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||||
|
m.Spec.Title,
|
||||||
|
m.Spec.Filters,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}, // default table converter
|
||||||
)
|
)
|
||||||
|
|
||||||
var ScopeDashboardBindingResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
var ScopeDashboardBindingResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
||||||
"scopedashboardbindings", "scopedashboardbinding", "ScopeDashboardBinding",
|
"scopedashboardbindings", "scopedashboardbinding", "ScopeDashboardBinding",
|
||||||
func() runtime.Object { return &ScopeDashboardBinding{} },
|
func() runtime.Object { return &ScopeDashboardBinding{} },
|
||||||
func() runtime.Object { return &ScopeDashboardBindingList{} },
|
func() runtime.Object { return &ScopeDashboardBindingList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
{Name: "Dashboard", Type: "string"},
|
||||||
|
{Name: "Scope", Type: "string"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
m, ok := obj.(*ScopeDashboardBinding)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("expected scope dashboard binding")
|
||||||
|
}
|
||||||
|
return []interface{}{
|
||||||
|
m.Name,
|
||||||
|
m.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||||
|
m.Spec.Dashboard,
|
||||||
|
m.Spec.Scope,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
var ScopeNodeResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
var ScopeNodeResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
||||||
"scopenodes", "scopenode", "ScopeNode",
|
"scopenodes", "scopenode", "ScopeNode",
|
||||||
func() runtime.Object { return &ScopeNode{} },
|
func() runtime.Object { return &ScopeNode{} },
|
||||||
func() runtime.Object { return &ScopeNodeList{} },
|
func() runtime.Object { return &ScopeNodeList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
{Name: "Title", Type: "string"},
|
||||||
|
{Name: "Parent Name", Type: "string"},
|
||||||
|
{Name: "Node Type", Type: "string"},
|
||||||
|
{Name: "Link Type", Type: "string"},
|
||||||
|
{Name: "Link ID", Type: "string"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
m, ok := obj.(*ScopeNode)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("expected scope node")
|
||||||
|
}
|
||||||
|
return []interface{}{
|
||||||
|
m.Name,
|
||||||
|
m.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||||
|
m.Spec.Title,
|
||||||
|
m.Spec.ParentName,
|
||||||
|
m.Spec.NodeType,
|
||||||
|
m.Spec.LinkType,
|
||||||
|
m.Spec.LinkID,
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}, // default table converter
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package v0alpha1
|
package v0alpha1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -18,6 +21,24 @@ var ExternalNameResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
|||||||
"externalnames", "externalname", "ExternalName",
|
"externalnames", "externalname", "ExternalName",
|
||||||
func() runtime.Object { return &ExternalName{} },
|
func() runtime.Object { return &ExternalName{} },
|
||||||
func() runtime.Object { return &ExternalNameList{} },
|
func() runtime.Object { return &ExternalNameList{} },
|
||||||
|
utils.TableColumns{
|
||||||
|
Definition: []metav1.TableColumnDefinition{
|
||||||
|
{Name: "Name", Type: "string", Format: "name"},
|
||||||
|
{Name: "Host", Type: "string", Format: "string", Description: "The service host"},
|
||||||
|
{Name: "Created At", Type: "date"},
|
||||||
|
},
|
||||||
|
Reader: func(obj any) ([]interface{}, error) {
|
||||||
|
m, ok := obj.(*ExternalName)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("expected external name")
|
||||||
|
}
|
||||||
|
return []interface{}{
|
||||||
|
m.Name,
|
||||||
|
m.Spec.Host,
|
||||||
|
m.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}, // default table converter
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -117,8 +117,13 @@ func doInstallPlugin(ctx context.Context, pluginID, version string, o pluginInst
|
|||||||
|
|
||||||
// If a version is specified, check if it is already installed
|
// If a version is specified, check if it is already installed
|
||||||
if version != "" {
|
if version != "" {
|
||||||
if services.PluginVersionInstalled(pluginID, version, o.pluginDir) {
|
if p, ok := services.PluginVersionInstalled(pluginID, version, o.pluginDir); ok {
|
||||||
services.Logger.Successf("Plugin %s v%s already installed.", pluginID, version)
|
services.Logger.Successf("Plugin %s v%s already installed.", pluginID, version)
|
||||||
|
for _, depP := range p.JSONData.Dependencies.Plugins {
|
||||||
|
if err := doInstallPlugin(ctx, depP.ID, depP.Version, o, installing); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -133,13 +138,28 @@ func doInstallPlugin(ctx context.Context, pluginID, version string, o pluginInst
|
|||||||
|
|
||||||
var archive *repo.PluginArchive
|
var archive *repo.PluginArchive
|
||||||
var err error
|
var err error
|
||||||
pluginZipURL := o.pluginURL
|
if o.pluginURL != "" {
|
||||||
if pluginZipURL != "" {
|
archive, err = repository.GetPluginArchiveByURL(ctx, o.pluginURL, compatOpts)
|
||||||
if archive, err = repository.GetPluginArchiveByURL(ctx, pluginZipURL, compatOpts); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if archive, err = repository.GetPluginArchive(ctx, pluginID, version, compatOpts); err != nil {
|
archiveInfo, err := repository.GetPluginArchiveInfo(ctx, pluginID, version, compatOpts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p, ok := services.PluginVersionInstalled(pluginID, archiveInfo.Version, o.pluginDir); ok {
|
||||||
|
services.Logger.Successf("Plugin %s v%s already installed.", pluginID, archiveInfo.Version)
|
||||||
|
for _, depP := range p.JSONData.Dependencies.Plugins {
|
||||||
|
if err = doInstallPlugin(ctx, depP.ID, depP.Version, o, installing); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if archive, err = repository.GetPluginArchiveByURL(ctx, archiveInfo.URL, compatOpts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,14 +88,14 @@ func GetLocalPlugins(pluginDir string) []*plugins.FoundBundle {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func PluginVersionInstalled(pluginID, version, pluginDir string) bool {
|
func PluginVersionInstalled(pluginID, version, pluginDir string) (plugins.FoundPlugin, bool) {
|
||||||
for _, bundle := range GetLocalPlugins(pluginDir) {
|
for _, bundle := range GetLocalPlugins(pluginDir) {
|
||||||
pJSON := bundle.Primary.JSONData
|
pJSON := bundle.Primary.JSONData
|
||||||
if pJSON.ID == pluginID {
|
if pJSON.ID == pluginID {
|
||||||
if pJSON.Info.Version == version {
|
if pJSON.Info.Version == version {
|
||||||
return true
|
return bundle.Primary, true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return plugins.FoundPlugin{}, false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
# apiserver-certificates
|
||||||
|
|
||||||
|
These certificates are used for development and testing ONLY. They are generated using the script under
|
||||||
|
[hack/make-aggregator-pki.sh](./hack/make-aggregator-pki.sh).
|
||||||
|
|
||||||
|
The CA, server and client certificates are each 10 years of expiration.
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIID9zCCAt+gAwIBAgIUeRrA5l+Rl4LkHPP1DmMFlYzrhW4wDQYJKoZIhvcNAQEL
|
||||||
|
BQAwgYoxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApOZXcgU3dlZGVuMRMwEQYDVQQH
|
||||||
|
DApTdG9ja2hvbG0gMRAwDgYDVQQKDAdHcmFmYW5hMQwwCgYDVQQLDANSJkQxEDAO
|
||||||
|
BgNVBAMMB3Rlc3QtY2ExHzAdBgkqhkiG9w0BCQEWEHRlc3RAZ3JhZmFuYS5hcHAw
|
||||||
|
HhcNMjQwODA1MjMxMzUzWhcNMzQwODAzMjMxMzUzWjCBijELMAkGA1UEBhMCVVMx
|
||||||
|
EzARBgNVBAgMCk5ldyBTd2VkZW4xEzARBgNVBAcMClN0b2NraG9sbSAxEDAOBgNV
|
||||||
|
BAoMB0dyYWZhbmExDDAKBgNVBAsMA1ImRDEQMA4GA1UEAwwHdGVzdC1jYTEfMB0G
|
||||||
|
CSqGSIb3DQEJARYQdGVzdEBncmFmYW5hLmFwcDCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||||
|
ggEPADCCAQoCggEBANj6qsutYwof0e0zHrp35Dey+kQxi+VTr/sAKlSoyySP4fmQ
|
||||||
|
9Qn8mDY4HyJ1oOJFpFAlD0Qp1xGdbvcrlvjoieqmfenW342fza0wqS5K8qkd2rJ7
|
||||||
|
khdAE2mACZTFSjmAa8+1rIRWnR0SaHBmDgdxBfNkET+n+cX+WsDMhmzNvPoPDS/V
|
||||||
|
8LaNih/eOUzb/5hamvD8CNLKakes0u/EsdxOsGFWCkpE1mg9yg0YPms5qUAj9pdV
|
||||||
|
iPH8B5zA1JoukZCrVGPv6R76fJI1LEohiASNFt9cgs2dhdk6QHzGyqNq3T3Cw8yI
|
||||||
|
Cug/Kk9DGqwq9OeXtADa4hhPebj04C4hxk0AT2UCAwEAAaNTMFEwHQYDVR0OBBYE
|
||||||
|
FBBn2SXiiItJQsJZ7MTvIn1s1t3jMB8GA1UdIwQYMBaAFBBn2SXiiItJQsJZ7MTv
|
||||||
|
In1s1t3jMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABcCcwsg
|
||||||
|
jrcNZheAaKXtdcZT01+doQhaOvhZQhY5L+bCnB0wkBXRJzv8ne+KgLx7auFQP8/2
|
||||||
|
OoPgaiA0R+XtImCkyBd+cr4mo2tYVpHt9+B0HaYGzGoXt0dre47ihlgkqoSwmgvG
|
||||||
|
9++pfrbQGRd5Xb/j0468sd5uQy1PPhsjCzFZTuxXcaAN13MDNikYjjn5mc5coklu
|
||||||
|
hCFH54PgP/PUDXxI0v/QUjNOj7hAdMkqOjzFD9Fze1KjtS3aSZvaaZVrM3x/YS8y
|
||||||
|
1IUgyocgoOKCqBOeEict+g/xghFDe7r2Dlgps/hPD1ojijBl83g5i079jW4y9jm+
|
||||||
|
osFOTGnRx2u0CpE=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDY+qrLrWMKH9Ht
|
||||||
|
Mx66d+Q3svpEMYvlU6/7ACpUqMskj+H5kPUJ/Jg2OB8idaDiRaRQJQ9EKdcRnW73
|
||||||
|
K5b46Inqpn3p1t+Nn82tMKkuSvKpHdqye5IXQBNpgAmUxUo5gGvPtayEVp0dEmhw
|
||||||
|
Zg4HcQXzZBE/p/nF/lrAzIZszbz6Dw0v1fC2jYof3jlM2/+YWprw/AjSympHrNLv
|
||||||
|
xLHcTrBhVgpKRNZoPcoNGD5rOalAI/aXVYjx/AecwNSaLpGQq1Rj7+ke+nySNSxK
|
||||||
|
IYgEjRbfXILNnYXZOkB8xsqjat09wsPMiAroPypPQxqsKvTnl7QA2uIYT3m49OAu
|
||||||
|
IcZNAE9lAgMBAAECggEAYUTyNy+J1BqSrd66WkZv6SZTeimp+Mrs+71FvMEUnFXi
|
||||||
|
LFJ2/xydEcVT88s+reEheYo7j0egcfWdLrH8UqZQWYB8ts0MV715YzgKx8Vyhizr
|
||||||
|
gxLRWZnwed2brfVJwoBXFHzxkzwO398GMckWZfCdhdBoyRwg5UkS3xZw9qq+mmw8
|
||||||
|
hvt88sDzHwnk/9rMY6KLNhWNiCSAxU65AlpktWRGy2e9wAtyzN+WX/iQwsiAyhk2
|
||||||
|
TV2bHQxRE7FT9hg4UBxzWruYRj4jkLKdH4tVqVKLsV7KutAOiYj3hCjYzOac/5QG
|
||||||
|
XrTRhN/ewAqJCTOI9K8BVT27l3QxREcq480auDwIawKBgQDzOr6+ILgPdqdGX9Nu
|
||||||
|
HoLnNuU6OdFsTJ3qXSp4AGv8ufq47PJmTp3F2ZN9RTDr6Fih7UKhApVvFOy6/Hlr
|
||||||
|
vb0MbL9FOe+2ejtnVmNi13AMexW+nmnOs1oSghXYIbt51/n+5bFmzAo6RFDEG0YT
|
||||||
|
mtRWMj4mifMI0XH31yRk+Yjp6wKBgQDkXxdG0OmoDwlrlbHjVOpEg7/A0diBHfb9
|
||||||
|
yh4MbaVmd6rGgdNkJYq1qB6ctJcLYpKq1QGgj6FyNDNyY/T4lIxRuSHqC2v1Mjt/
|
||||||
|
TaL1BhMsH2Q6+bT8mlmM4cNNgSVHfISJv2mMuKfEqb+uY0y8UU6/lYmwzC5rH4bs
|
||||||
|
TgPNSLmH7wKBgQCk8F9M2y81/UZt6Kmd8T7fwFAt7etgP4yO02LrQY35Mb0eDkBK
|
||||||
|
tGE1O9hSiMsmDseb9yLJwNDJJS1rl65XK7G5bT0/mow9+CG0b9axvlqTfBxAyXgC
|
||||||
|
3YjlKCXcDPPvKlCzU9u7U/5TiOQkOEKLJOF9GlEfHUkb37wjT1e0yarYxQKBgFNF
|
||||||
|
nT40RU8DlKLHJeNH/lhXVh9gJTsHix2Fiqlrfck8T2gsxMEas1aD5A2uB/mdyu9B
|
||||||
|
1mMOnIcBI9VNP3E48WWHRSeLXKU+2NUVoRsJSQpos+qRTP5i5c5qMAXd1pMXg1ib
|
||||||
|
FEi8uGgMoZlcGgn89+MCCwANo8tp5o/Z7qb3Ire/AoGANmc8BAGgKanJVAfu7gDU
|
||||||
|
AgxRg5T0/C6vpWh5k/gFS7AQqgnx8EcRYtuCD4Er5GFRdcyb9AuZccyKzWI5JS89
|
||||||
|
YOmJPrfDd7VzBsrOjwJCXcV6tUUq6yO4Ra8vKlIw7T+mMYZh7xWwGnT8UA+X9F+7
|
||||||
|
yV6kPpraMD/dFbo2TZoNMjc=
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIEjDCCA3SgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBijELMAkGA1UEBhMCVVMx
|
||||||
|
EzARBgNVBAgMCk5ldyBTd2VkZW4xEzARBgNVBAcMClN0b2NraG9sbSAxEDAOBgNV
|
||||||
|
BAoMB0dyYWZhbmExDDAKBgNVBAsMA1ImRDEQMA4GA1UEAwwHdGVzdC1jYTEfMB0G
|
||||||
|
CSqGSIb3DQEJARYQdGVzdEBncmFmYW5hLmFwcDAeFw0yNDA4MDUyMzEzNTRaFw0z
|
||||||
|
NDA4MDMyMzEzNTRaMC8xFDASBgNVBAMMC2RldmVsb3BtZW50MRcwFQYDVQQKDA5z
|
||||||
|
eXN0ZW06bWFzdGVyczCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMz3
|
||||||
|
SSM62uPCNG7mbdlgE0bYcpT8f1ZFmyaauXzbqdTF0/VxGCh9YVBFnqlAdfAUFasN
|
||||||
|
HBLQz63PniOWbzmhXZBkrCF/nozg7PgiXJ7aM5ppJyYulOI3sldH49V8KWzcGiJo
|
||||||
|
ZE13kKIlCqnOanErj4RnaxVJvMmWOlt6Zn/ljkDFvwypaOeIX/YO1zuZ95BqGpDh
|
||||||
|
u4CXipKeCM0AjXNvKrHCi5QbNEF0FMyvsX+s76wZtZSpB7vbIfj/h5Z4Scfyovmy
|
||||||
|
cZeqgL2mDAnosKw8/KYAgkcArMHoScTfdCjWthJQlPybJKgMKOJI9OehuP1a89A9
|
||||||
|
t+T/T8ZT75x0YADw+56WDcsYGECiaixrkpB0aiPBtgDm1i1z7ooqSfAfZa0PEgPT
|
||||||
|
L680TK66qXI2GFtNqUkU6+xZEfvuxOdZRXkuIVeQ4UMqaiW9y37ppvKzX4llOD2D
|
||||||
|
VP86D0JxCCuJqxlzSAGbxK61c8mdqM1SKAa5O5Kl0KHxVkGk4zHYtrHqWsy6YOIM
|
||||||
|
CqLMA2vLAeqYwSEn2eQBkTNdhSulrB6JPNBDPNB9+wX5EOX3w+x9lRxn59kQgKyV
|
||||||
|
anf1cm1UYeqjsLPhBjpuwVtHhEAc8uJrSZBM3oKPSYEYxwEo+QG8rcKIpjV9gtCu
|
||||||
|
T3e/WBWlDVBm0NwbxtY/rYc5ggPHMtPhFxBDhjJpAgMBAAGjVzBVMBMGA1UdJQQM
|
||||||
|
MAoGCCsGAQUFBwMCMB0GA1UdDgQWBBQ+z7tF4/VANXrXgsY0PVQFFo6csjAfBgNV
|
||||||
|
HSMEGDAWgBQQZ9kl4oiLSULCWezE7yJ9bNbd4zANBgkqhkiG9w0BAQsFAAOCAQEA
|
||||||
|
NagU3hqJr5wEU79202Rj+aqbzWJlz1jvZVR6PHILB0deTtvYk1EXeVgyjwmz7PW3
|
||||||
|
DLYcpwgW3bTdxhejduFNzKazDOZZ2blUlZlHs1PoBb/ipnw4g+ozO+ZyGs05gCu8
|
||||||
|
DrsxUKX7bpVcKGfPNVg8L4xZbanizO1XUiv6PDBBRZhKXSl+KO9+aN2C/yRnYmpV
|
||||||
|
9dyuMI9nFoMB0K7rxTxiRCIPIWs8nsGouLa6lg6/I+xTAjV0IqNz0rQ66UWJOr3x
|
||||||
|
vLGFdMMaDUSbsNlu18/sCJd+G0rkh24YE6e3I1wGqE9jr4iYsMhkhfp0u4Qojmfp
|
||||||
|
3/7IGZYzvVIN/PvSFBBgZw==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIEmjCCAoICAQAwLzEUMBIGA1UEAwwLZGV2ZWxvcG1lbnQxFzAVBgNVBAoMDnN5
|
||||||
|
c3RlbTptYXN0ZXJzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzPdJ
|
||||||
|
Izra48I0buZt2WATRthylPx/VkWbJpq5fNup1MXT9XEYKH1hUEWeqUB18BQVqw0c
|
||||||
|
EtDPrc+eI5ZvOaFdkGSsIX+ejODs+CJcntozmmknJi6U4jeyV0fj1XwpbNwaImhk
|
||||||
|
TXeQoiUKqc5qcSuPhGdrFUm8yZY6W3pmf+WOQMW/DKlo54hf9g7XO5n3kGoakOG7
|
||||||
|
gJeKkp4IzQCNc28qscKLlBs0QXQUzK+xf6zvrBm1lKkHu9sh+P+HlnhJx/Ki+bJx
|
||||||
|
l6qAvaYMCeiwrDz8pgCCRwCswehJxN90KNa2ElCU/JskqAwo4kj056G4/Vrz0D23
|
||||||
|
5P9PxlPvnHRgAPD7npYNyxgYQKJqLGuSkHRqI8G2AObWLXPuiipJ8B9lrQ8SA9Mv
|
||||||
|
rzRMrrqpcjYYW02pSRTr7FkR++7E51lFeS4hV5DhQypqJb3Lfumm8rNfiWU4PYNU
|
||||||
|
/zoPQnEIK4mrGXNIAZvErrVzyZ2ozVIoBrk7kqXQofFWQaTjMdi2sepazLpg4gwK
|
||||||
|
oswDa8sB6pjBISfZ5AGRM12FK6WsHok80EM80H37BfkQ5ffD7H2VHGfn2RCArJVq
|
||||||
|
d/VybVRh6qOws+EGOm7BW0eEQBzy4mtJkEzego9JgRjHASj5AbytwoimNX2C0K5P
|
||||||
|
d79YFaUNUGbQ3BvG1j+thzmCA8cy0+EXEEOGMmkCAwEAAaAmMCQGCSqGSIb3DQEJ
|
||||||
|
DjEXMBUwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQELBQADggIBAB7x
|
||||||
|
82Hy942/7nSbz9gJUsJ70sYcre0VRduIMzP1Ytk/ni+1QXDZSmdSrgR0cbTL4VIZ
|
||||||
|
zSt5Bp2vxBJqHmGPOXv5oWI+nGrfWzmzBGoGNk4cdNW2ZZCn6Om4L16gLYIlxmm1
|
||||||
|
vgCgnPOYMEi5qtgG6676wSX7gf5+j1gWVDEwNFil8wBUIlA9QRbc2NN0T6479Z4b
|
||||||
|
4tUHzUpGuf788OPUqAdxpkH1xZBKW8HEGsk1+NdHgb5krE2ElwvN9qWX5f2DzzwL
|
||||||
|
sQf3A2IrO4lrso4RHao4X3V+/DOZCx+FK/oXqzyoS4YNnNy6B0377LgYkuuJsbOo
|
||||||
|
IFC7vRhBORvAXEXD/scLEsGKuQzs1vLLiwOp7pBOcGZzxOoqoXc2A6h64qpZKriq
|
||||||
|
T1rK6rmrttaXqYUokjbg/ggpcXCC1BGxsoRvGQoN6aQNV27zUh2wpZqpCpVLoN21
|
||||||
|
eCqM9LVoPMCRqn7ItXE9oJhasPlKDO6amHL3CtxjvU2meXoa+nCia8lL4Sb9pSkB
|
||||||
|
O5eX4k3H/m4zjrpbqp7UdnidcYn4zfrjRBr65bqNr1sOxwUDWOa0kqaOKrgqwhWn
|
||||||
|
ld6zkuekuwzrOK4+Dpf5ybWnVFi8WSz6k9TqQIaeMrzeCBVboufl/ygoIRsTqp7N
|
||||||
|
WcKuZdUx8t16hFZ/NgpHRgfZI9pYdzi2vNSZ6WMM
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
nL1C44m2dHWfIuDuGDs008g/12A53A==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIE0jCCA7qgAwIBAgIBAjANBgkqhkiG9w0BAQsFADCBijELMAkGA1UEBhMCVVMx
|
||||||
|
EzARBgNVBAgMCk5ldyBTd2VkZW4xEzARBgNVBAcMClN0b2NraG9sbSAxEDAOBgNV
|
||||||
|
BAoMB0dyYWZhbmExDDAKBgNVBAsMA1ImRDEQMA4GA1UEAwwHdGVzdC1jYTEfMB0G
|
||||||
|
CSqGSIb3DQEJARYQdGVzdEBncmFmYW5hLmFwcDAeFw0yNDA4MDUyMzEzNTVaFw0z
|
||||||
|
NDA4MDMyMzEzNTVaMCkxEjAQBgNVBAMMCWxvY2FsaG9zdDETMBEGA1UECgwKYWdn
|
||||||
|
cmVnYXRlZDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMbgGuvCKonn
|
||||||
|
9KgW58wqgxiykVbbmT82HmhgOBw5CSg7lVnKQlRzfvUtwZX7UhHXYxOCu0no7RqD
|
||||||
|
Y19QiGOx6SQlaXSMxqPL1PpWMQdX6j/I/H1pHMJ8Ynyd956VlhKUOIQBJMVd46Bg
|
||||||
|
6IEU4izuJ9uqEuHQrO8lb2eaTXAjsOmtIImxBI7xFsxp42wJyo6ptGetyaNCw7UF
|
||||||
|
e8xYmBVSLyfiN/drBx/lcwMiDgzfzf8BsavC96Fa/HujXkybAIyVid2DxKODPT9+
|
||||||
|
7MA1ZY0/Nz3ySKSxIIbfL+1cPihR2GGy1Qwa/GuuJSGAwzy6D/9am5/K3yR927Bk
|
||||||
|
OgMUCIlpGlSDY1qFd79o/n9BJ9FJmlTMgIZ6RokaXAU2uaiE6v7YPYGkXoIz3ncc
|
||||||
|
SBprVom2h3RmM3qDbmXio1gGsDL/2SI+Hpnq2zhCzL28K+bZA6ukYpCNyyaYF9O6
|
||||||
|
X6qi56zGCw/igTJgaYKMsNMSoX+3eHSFxj5JeF4bWcC4qTqmzvfz0HhvX+jjky/V
|
||||||
|
LHCh9ddH1umT/Ss/IxaNarXxXHoUmN33FgFQ+tML4eTvosG6IxUdJ/34qBT+dWk4
|
||||||
|
zNIhStTUNiYPbcSfv8qKLHhMAuZOLsTATLsFQt7bzGZwV8HAuCiqzjg9p4R/dRjE
|
||||||
|
OLhQLZfKkYl99Mgli6v/hooEBSORCulVAgMBAAGjgaIwgZ8wPgYDVR0RBDcwNYIo
|
||||||
|
djBhbHBoYTEuZXhhbXBsZS5ncmFmYW5hLmFwcC5kZWZhdWx0LnN2Y4IJbG9jYWxo
|
||||||
|
b3N0MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAdBgNVHQ4EFgQUFvrZ
|
||||||
|
B0vSR3B7HcNPCZrZ2H7sFw0wHwYDVR0jBBgwFoAUEGfZJeKIi0lCwlnsxO8ifWzW
|
||||||
|
3eMwDQYJKoZIhvcNAQELBQADggEBAGYZYrY+eFmgwFp/ohIcWnekdOiCr7qjnmRo
|
||||||
|
LyNU/EbFz0HUV+yTrtoXLjv8S9D9Yc029Dgj2f31Hp7cOGjUfd0t1DgTBChcFgVr
|
||||||
|
E5ZbhmKEM0tQiBMI6mHvy6hFT+nc9/yftnndHRUyR4xm6E1dMFqpMyMdYKojRmbn
|
||||||
|
F6znVTcjBr4OiDnfTUkqYO8kc3I0qvA5ou4jXAJ9mu3UEbEjwc6C2/Mrr48a42Df
|
||||||
|
rHrwqwnM3DC2+SWVocctk1PRZqMFWypJ9U/HbKPbId79YyHNsI0XtxizV2sX5oXD
|
||||||
|
1NPPEBeEXZ3Nv/gn4d0h57UoBML9165fRRQSe7CkIl8kZigHGCg=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
-----BEGIN CERTIFICATE REQUEST-----
|
||||||
|
MIIE3jCCAsYCAQAwKTESMBAGA1UEAwwJbG9jYWxob3N0MRMwEQYDVQQKDAphZ2dy
|
||||||
|
ZWdhdGVkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxuAa68Iqief0
|
||||||
|
qBbnzCqDGLKRVtuZPzYeaGA4HDkJKDuVWcpCVHN+9S3BlftSEddjE4K7SejtGoNj
|
||||||
|
X1CIY7HpJCVpdIzGo8vU+lYxB1fqP8j8fWkcwnxifJ33npWWEpQ4hAEkxV3joGDo
|
||||||
|
gRTiLO4n26oS4dCs7yVvZ5pNcCOw6a0gibEEjvEWzGnjbAnKjqm0Z63Jo0LDtQV7
|
||||||
|
zFiYFVIvJ+I392sHH+VzAyIODN/N/wGxq8L3oVr8e6NeTJsAjJWJ3YPEo4M9P37s
|
||||||
|
wDVljT83PfJIpLEght8v7Vw+KFHYYbLVDBr8a64lIYDDPLoP/1qbn8rfJH3bsGQ6
|
||||||
|
AxQIiWkaVINjWoV3v2j+f0En0UmaVMyAhnpGiRpcBTa5qITq/tg9gaRegjPedxxI
|
||||||
|
GmtWibaHdGYzeoNuZeKjWAawMv/ZIj4emerbOELMvbwr5tkDq6RikI3LJpgX07pf
|
||||||
|
qqLnrMYLD+KBMmBpgoyw0xKhf7d4dIXGPkl4XhtZwLipOqbO9/PQeG9f6OOTL9Us
|
||||||
|
cKH110fW6ZP9Kz8jFo1qtfFcehSY3fcWAVD60wvh5O+iwbojFR0n/fioFP51aTjM
|
||||||
|
0iFK1NQ2Jg9txJ+/yooseEwC5k4uxMBMuwVC3tvMZnBXwcC4KKrOOD2nhH91GMQ4
|
||||||
|
uFAtl8qRiX30yCWLq/+GigQFI5EK6VUCAwEAAaBwMG4GCSqGSIb3DQEJDjFhMF8w
|
||||||
|
PgYDVR0RBDcwNYIodjBhbHBoYTEuZXhhbXBsZS5ncmFmYW5hLmFwcC5kZWZhdWx0
|
||||||
|
LnN2Y4IJbG9jYWxob3N0MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAN
|
||||||
|
BgkqhkiG9w0BAQsFAAOCAgEAGmr6CnGfcm+DapmhQnQLyq0HyCqKD0PKKamofQLb
|
||||||
|
YVMiCF/zKDV4+gIs2kMK3uWeZ9r4xDa9nEgBw6U2vi14AI79hmpETCrfvK6hterI
|
||||||
|
Znb2TQMYnUY3rt26DFNf3/21/jb/1cn/9z55TaHJGqqlJmvB1LfYJoMN8t6A6xg4
|
||||||
|
J8TYjBuwQQOqFAPRuxmGag44PSC9V5e6gajz56RPyZz2kmdbfPZRNCnqileDWZla
|
||||||
|
7pilwP8QAhrJCPP25edc/5hP2WNTEH/GTa5FFmkNMKEHn6+dnBuMu5w1SygKUYWz
|
||||||
|
37qE7jntZC/RGVZ//npwsVyaa+NbgJNjhg/EMj+sWb/Eet2ETq7v9FCM0QG3HNUk
|
||||||
|
6d6af2YHI3Fo89y5ty1DOydBa5lIxy6gDTwameJYoTem71nPlRtU2b4VFBWZ4xwE
|
||||||
|
ac7Xmon+Z7tOHVwcCPp1cTwJ2TNwha0JxsW0C1g3QG59ILU5FMPlqOuDJi41uWql
|
||||||
|
Q56O+a6MnK7GfGxBMMf4FSlbV3xjUGxqyGy5KwIcsy+u/3axCWPUcoz3g0pApbGO
|
||||||
|
pfsu5Ptr/xMtYjLRXbEcH9Byqx/LrBvD2upwNnlfMtgWIlg/EnZ26Mgardu/NwQq
|
||||||
|
3fpYv00VWbvBE/B/5p5zmEpA67COFejiwVTsbeN345Ue2mq59DNg6BZmaxKH3Sbq
|
||||||
|
Kz8=
|
||||||
|
-----END CERTIFICATE REQUEST-----
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
C+DUGz2rplZQ/4PtNOcz7reeAFvu6A==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
_ "github.com/googleapis/gax-go/v2"
|
_ "github.com/googleapis/gax-go/v2"
|
||||||
_ "github.com/grafana/dskit/backoff"
|
_ "github.com/grafana/dskit/backoff"
|
||||||
_ "github.com/grafana/dskit/flagext"
|
_ "github.com/grafana/dskit/flagext"
|
||||||
|
_ "github.com/grafana/e2e"
|
||||||
_ "github.com/grafana/gofpdf"
|
_ "github.com/grafana/gofpdf"
|
||||||
_ "github.com/grafana/gomemcache/memcache"
|
_ "github.com/grafana/gomemcache/memcache"
|
||||||
_ "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
|
_ "github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus"
|
||||||
|
|||||||
@@ -122,22 +122,6 @@ func (dc *databaseCache) Delete(ctx context.Context, key string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (dc *databaseCache) Count(ctx context.Context, prefix string) (int64, error) {
|
|
||||||
res := int64(0)
|
|
||||||
err := dc.SQLStore.WithDbSession(ctx, func(session *db.Session) error {
|
|
||||||
sql := "SELECT COUNT(*) FROM cache_data WHERE cache_key LIKE ?"
|
|
||||||
|
|
||||||
_, err := session.SQL(sql, prefix+"%").Get(&res)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CacheData is the struct representing the table in the database
|
// CacheData is the struct representing the table in the database
|
||||||
type CacheData struct {
|
type CacheData struct {
|
||||||
CacheKey string
|
CacheKey string
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
@@ -76,42 +75,3 @@ func TestSecondSet(t *testing.T) {
|
|||||||
err = db.Set(context.Background(), "killa-gorilla", obj, 0)
|
err = db.Set(context.Background(), "killa-gorilla", obj, 0)
|
||||||
assert.Equal(t, err, nil)
|
assert.Equal(t, err, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDatabaseStorageCount(t *testing.T) {
|
|
||||||
sqlstore := db.InitTestDB(t)
|
|
||||||
|
|
||||||
db := &databaseCache{
|
|
||||||
SQLStore: sqlstore,
|
|
||||||
log: log.New("remotecache.database"),
|
|
||||||
}
|
|
||||||
|
|
||||||
obj := []byte("foolbar")
|
|
||||||
|
|
||||||
// set time.now to 2 weeks ago
|
|
||||||
var err error
|
|
||||||
getTime = func() time.Time { return time.Now().AddDate(0, 0, -2) }
|
|
||||||
err = db.Set(context.Background(), "pref-key1", obj, 1000*time.Second)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = db.Set(context.Background(), "pref-key2", obj, 1000*time.Second)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = db.Set(context.Background(), "pref-key3", obj, 1000*time.Second)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// insert object that should never expire
|
|
||||||
err = db.Set(context.Background(), "pref-key4", obj, 0)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
getTime = time.Now
|
|
||||||
err = db.Set(context.Background(), "pref-key5", obj, 1000*time.Second)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// run GC
|
|
||||||
db.internalRunGC()
|
|
||||||
|
|
||||||
// try to read values
|
|
||||||
n, errC := db.Count(context.Background(), "pref-")
|
|
||||||
require.NoError(t, errC)
|
|
||||||
assert.Equal(t, int64(2), n)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -57,10 +57,6 @@ func (s *memcachedStorage) Get(ctx context.Context, key string) ([]byte, error)
|
|||||||
return memcachedItem.Value, nil
|
return memcachedItem.Value, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *memcachedStorage) Count(ctx context.Context, prefix string) (int64, error) {
|
|
||||||
return 0, ErrNotImplemented
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete delete a key from the cache
|
// Delete delete a key from the cache
|
||||||
func (s *memcachedStorage) Delete(ctx context.Context, key string) error {
|
func (s *memcachedStorage) Delete(ctx context.Context, key string) error {
|
||||||
return s.c.Delete(key)
|
return s.c.Delete(key)
|
||||||
|
|||||||
@@ -20,5 +20,4 @@ func TestIntegrationMemcachedCacheStorage(t *testing.T) {
|
|||||||
opts := &setting.RemoteCacheOptions{Name: memcachedCacheType, ConnStr: u}
|
opts := &setting.RemoteCacheOptions{Name: memcachedCacheType, ConnStr: u}
|
||||||
client := createTestClient(t, opts, nil)
|
client := createTestClient(t, opts, nil)
|
||||||
runTestsForClient(t, client)
|
runTestsForClient(t, client)
|
||||||
runCountTestsForClient(t, opts, nil)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,12 +110,3 @@ func (s *redisStorage) Delete(ctx context.Context, key string) error {
|
|||||||
cmd := s.c.Del(ctx, key)
|
cmd := s.c.Del(ctx, key)
|
||||||
return cmd.Err()
|
return cmd.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *redisStorage) Count(ctx context.Context, prefix string) (int64, error) {
|
|
||||||
cmd := s.c.Keys(ctx, prefix+"*")
|
|
||||||
if cmd.Err() != nil {
|
|
||||||
return 0, cmd.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
return int64(len(cmd.Val())), nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -37,5 +37,4 @@ func TestIntegrationRedisCacheStorage(t *testing.T) {
|
|||||||
opts := &setting.RemoteCacheOptions{Name: redisCacheType, ConnStr: b.String()}
|
opts := &setting.RemoteCacheOptions{Name: redisCacheType, ConnStr: b.String()}
|
||||||
client := createTestClient(t, opts, nil)
|
client := createTestClient(t, opts, nil)
|
||||||
runTestsForClient(t, client)
|
runTestsForClient(t, client)
|
||||||
runCountTestsForClient(t, opts, nil)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,11 +69,6 @@ type CacheStorage interface {
|
|||||||
|
|
||||||
// Delete object from cache
|
// Delete object from cache
|
||||||
Delete(ctx context.Context, key string) error
|
Delete(ctx context.Context, key string) error
|
||||||
|
|
||||||
// Count returns the number of items in the cache.
|
|
||||||
// Optionaly a prefix can be provided to only count items with that prefix
|
|
||||||
// DO NOT USE. Not available for memcached.
|
|
||||||
Count(ctx context.Context, prefix string) (int64, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoteCache allows Grafana to cache data outside its own process
|
// RemoteCache allows Grafana to cache data outside its own process
|
||||||
@@ -102,11 +97,6 @@ func (ds *RemoteCache) Delete(ctx context.Context, key string) error {
|
|||||||
return ds.client.Delete(ctx, key)
|
return ds.client.Delete(ctx, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count returns the number of items in the cache.
|
|
||||||
func (ds *RemoteCache) Count(ctx context.Context, prefix string) (int64, error) {
|
|
||||||
return ds.client.Count(ctx, prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run starts the backend processes for cache clients.
|
// Run starts the backend processes for cache clients.
|
||||||
func (ds *RemoteCache) Run(ctx context.Context) error {
|
func (ds *RemoteCache) Run(ctx context.Context) error {
|
||||||
// create new interface if more clients need GC jobs
|
// create new interface if more clients need GC jobs
|
||||||
@@ -173,10 +163,6 @@ func (pcs *encryptedCacheStorage) Delete(ctx context.Context, key string) error
|
|||||||
return pcs.cache.Delete(ctx, key)
|
return pcs.cache.Delete(ctx, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pcs *encryptedCacheStorage) Count(ctx context.Context, prefix string) (int64, error) {
|
|
||||||
return pcs.cache.Count(ctx, prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
type prefixCacheStorage struct {
|
type prefixCacheStorage struct {
|
||||||
cache CacheStorage
|
cache CacheStorage
|
||||||
prefix string
|
prefix string
|
||||||
@@ -191,7 +177,3 @@ func (pcs *prefixCacheStorage) Set(ctx context.Context, key string, value []byte
|
|||||||
func (pcs *prefixCacheStorage) Delete(ctx context.Context, key string) error {
|
func (pcs *prefixCacheStorage) Delete(ctx context.Context, key string) error {
|
||||||
return pcs.cache.Delete(ctx, pcs.prefix+key)
|
return pcs.cache.Delete(ctx, pcs.prefix+key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pcs *prefixCacheStorage) Count(ctx context.Context, prefix string) (int64, error) {
|
|
||||||
return pcs.cache.Count(ctx, pcs.prefix+prefix)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ func TestCachedBasedOnConfig(t *testing.T) {
|
|||||||
|
|
||||||
client := createTestClient(t, cfg.RemoteCacheOptions, db)
|
client := createTestClient(t, cfg.RemoteCacheOptions, db)
|
||||||
runTestsForClient(t, client)
|
runTestsForClient(t, client)
|
||||||
runCountTestsForClient(t, cfg.RemoteCacheOptions, db)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInvalidCacheTypeReturnsError(t *testing.T) {
|
func TestInvalidCacheTypeReturnsError(t *testing.T) {
|
||||||
@@ -55,37 +54,6 @@ func runTestsForClient(t *testing.T, client CacheStorage) {
|
|||||||
canNotFetchExpiredItems(t, client)
|
canNotFetchExpiredItems(t, client)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCountTestsForClient(t *testing.T, opts *setting.RemoteCacheOptions, sqlstore db.DB) {
|
|
||||||
client := createTestClient(t, opts, sqlstore)
|
|
||||||
expectError := false
|
|
||||||
if opts.Name == memcachedCacheType {
|
|
||||||
expectError = true
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("can count items", func(t *testing.T) {
|
|
||||||
cacheableValue := []byte("hej hej")
|
|
||||||
|
|
||||||
err := client.Set(context.Background(), "pref-key1", cacheableValue, 0)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = client.Set(context.Background(), "pref-key2", cacheableValue, 0)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = client.Set(context.Background(), "key3-not-pref", cacheableValue, 0)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
n, errC := client.Count(context.Background(), "pref-")
|
|
||||||
if expectError {
|
|
||||||
require.ErrorIs(t, ErrNotImplemented, errC)
|
|
||||||
assert.Equal(t, int64(0), n)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
require.NoError(t, errC)
|
|
||||||
assert.Equal(t, int64(2), n)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func canPutGetAndDeleteCachedObjects(t *testing.T, client CacheStorage) {
|
func canPutGetAndDeleteCachedObjects(t *testing.T, client CacheStorage) {
|
||||||
dataToCache := []byte("some bytes")
|
dataToCache := []byte("some bytes")
|
||||||
|
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ func (fcs FakeCacheStorage) Delete(_ context.Context, key string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fcs FakeCacheStorage) Count(_ context.Context, prefix string) (int64, error) {
|
|
||||||
return int64(len(fcs.Storage)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFakeCacheStorage() FakeCacheStorage {
|
func NewFakeCacheStorage() FakeCacheStorage {
|
||||||
return FakeCacheStorage{
|
return FakeCacheStorage{
|
||||||
Storage: map[string][]byte{},
|
Storage: map[string][]byte{},
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ func readPluginJSON(pluginDir string) (plugins.JSONData, error) {
|
|||||||
// nolint:gosec
|
// nolint:gosec
|
||||||
data, err = os.ReadFile(pluginPath)
|
data, err = os.ReadFile(pluginPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return plugins.JSONData{}, fmt.Errorf("could not find plugin.json or dist/plugin.json for in %s", pluginDir)
|
return plugins.JSONData{}, fmt.Errorf("could not find plugin.json or dist/plugin.json in %s", pluginDir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/types"
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
|
||||||
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
model "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
model "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
@@ -43,11 +44,16 @@ func convertToK8sResource(orgID int64, receiver definitions.GettableApiReceiver,
|
|||||||
return nil, fmt.Errorf("all integrations must have the same provenance")
|
return nil, fmt.Errorf("all integrations must have the same provenance")
|
||||||
}
|
}
|
||||||
provenance = integration.Provenance
|
provenance = integration.Provenance
|
||||||
|
unstruct := common.Unstructured{}
|
||||||
|
err := json.Unmarshal(integration.Settings, &unstruct)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("integration '%s' of receiver '%s' has settings that cannot be parsed as JSON: %w", integration.Type, receiver.Name, err)
|
||||||
|
}
|
||||||
spec.Integrations = append(spec.Integrations, model.Integration{
|
spec.Integrations = append(spec.Integrations, model.Integration{
|
||||||
Uid: &integration.UID,
|
Uid: &integration.UID,
|
||||||
Type: integration.Type,
|
Type: integration.Type,
|
||||||
DisableResolveMessage: &integration.DisableResolveMessage,
|
DisableResolveMessage: &integration.DisableResolveMessage,
|
||||||
Settings: json.RawMessage(integration.Settings),
|
Settings: unstruct,
|
||||||
SecureFields: integration.SecureFields,
|
SecureFields: integration.SecureFields,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -79,12 +85,16 @@ func convertToDomainModel(receiver *model.Receiver) (definitions.GettableApiRece
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, integration := range receiver.Spec.Integrations {
|
for _, integration := range receiver.Spec.Integrations {
|
||||||
|
data, err := integration.Settings.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
return definitions.GettableApiReceiver{}, fmt.Errorf("integration '%s' of receiver '%s' is invalid: failed to convert unstructured data to bytes: %w", integration.Type, receiver.Name, err)
|
||||||
|
}
|
||||||
grafanaIntegration := definitions.GettableGrafanaReceiver{
|
grafanaIntegration := definitions.GettableGrafanaReceiver{
|
||||||
Name: receiver.Spec.Title,
|
Name: receiver.Spec.Title,
|
||||||
Type: integration.Type,
|
Type: integration.Type,
|
||||||
Settings: definitions.RawMessage(integration.Settings),
|
Settings: definitions.RawMessage(data),
|
||||||
SecureFields: integration.SecureFields,
|
SecureFields: integration.SecureFields,
|
||||||
//Provenance: "", //TODO: Convert provenance?
|
Provenance: definitions.Provenance(models.ProvenanceNone),
|
||||||
}
|
}
|
||||||
if integration.Uid != nil {
|
if integration.Uid != nil {
|
||||||
grafanaIntegration.UID = *integration.Uid
|
grafanaIntegration.UID = *integration.Uid
|
||||||
|
|||||||
@@ -1,19 +1,14 @@
|
|||||||
package receiver
|
package receiver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
|
||||||
model "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
|
||||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ grafanarest.Storage = (*storage)(nil)
|
var _ grafanarest.Storage = (*storage)(nil)
|
||||||
@@ -35,25 +30,9 @@ func NewStorage(
|
|||||||
dualWriteBuilder grafanarest.DualWriteBuilder,
|
dualWriteBuilder grafanarest.DualWriteBuilder,
|
||||||
) (rest.Storage, error) {
|
) (rest.Storage, error) {
|
||||||
legacyStore := &legacyStorage{
|
legacyStore := &legacyStorage{
|
||||||
service: legacySvc,
|
service: legacySvc,
|
||||||
namespacer: namespacer,
|
namespacer: namespacer,
|
||||||
tableConverter: utils.NewTableConverter(
|
tableConverter: resourceInfo.TableConverter(),
|
||||||
resourceInfo.GroupResource(),
|
|
||||||
[]metav1.TableColumnDefinition{
|
|
||||||
{Name: "Name", Type: "string", Format: "name"},
|
|
||||||
{Name: "Title", Type: "string", Format: "string", Description: "The receiver name"}, // TODO: Add integration types.
|
|
||||||
},
|
|
||||||
func(obj any) ([]interface{}, error) {
|
|
||||||
r, ok := obj.(*model.Receiver)
|
|
||||||
if ok {
|
|
||||||
return []interface{}{
|
|
||||||
r.Name,
|
|
||||||
r.Spec.Title,
|
|
||||||
// r.Spec, //TODO implement formatting for Spec, same as UI?
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("expected resource or info")
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
if optsGetter != nil && dualWriteBuilder != nil {
|
if optsGetter != nil && dualWriteBuilder != nil {
|
||||||
strategy := grafanaregistry.NewStrategy(scheme)
|
strategy := grafanaregistry.NewStrategy(scheme)
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
"k8s.io/kube-openapi/pkg/common"
|
"k8s.io/kube-openapi/pkg/common"
|
||||||
|
"k8s.io/kube-openapi/pkg/spec3"
|
||||||
|
|
||||||
notificationsModels "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
notificationsModels "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||||
@@ -55,11 +56,11 @@ func RegisterAPIService(
|
|||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t NotificationsAPIBuilder) GetGroupVersion() schema.GroupVersion {
|
func (t *NotificationsAPIBuilder) GetGroupVersion() schema.GroupVersion {
|
||||||
return t.gv
|
return t.gv
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t NotificationsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
func (t *NotificationsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
||||||
err := notificationsModels.AddToScheme(scheme)
|
err := notificationsModels.AddToScheme(scheme)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -67,7 +68,7 @@ func (t NotificationsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
|||||||
return scheme.SetVersionPriority(notificationsModels.SchemeGroupVersion)
|
return scheme.SetVersionPriority(notificationsModels.SchemeGroupVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t NotificationsAPIBuilder) GetAPIGroupInfo(
|
func (t *NotificationsAPIBuilder) GetAPIGroupInfo(
|
||||||
scheme *runtime.Scheme,
|
scheme *runtime.Scheme,
|
||||||
codecs serializer.CodecFactory,
|
codecs serializer.CodecFactory,
|
||||||
optsGetter generic.RESTOptionsGetter,
|
optsGetter generic.RESTOptionsGetter,
|
||||||
@@ -92,15 +93,35 @@ func (t NotificationsAPIBuilder) GetAPIGroupInfo(
|
|||||||
return &apiGroupInfo, nil
|
return &apiGroupInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t NotificationsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
|
func (t *NotificationsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
|
||||||
return notificationsModels.GetOpenAPIDefinitions
|
return notificationsModels.GetOpenAPIDefinitions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t NotificationsAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
|
func (t *NotificationsAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t NotificationsAPIBuilder) GetAuthorizer() authorizer.Authorizer {
|
// PostProcessOpenAPI is a hook to alter OpenAPI3 specification of the API server.
|
||||||
|
func (t *NotificationsAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
||||||
|
// The plugin description
|
||||||
|
oas.Info.Description = "Grafana Alerting Notification resources"
|
||||||
|
|
||||||
|
// The root api URL
|
||||||
|
root := "/apis/" + t.GetGroupVersion().String() + "/"
|
||||||
|
|
||||||
|
// Hide the ability to list or watch across all tenants
|
||||||
|
delete(oas.Paths.Paths, root+notificationsModels.ReceiverResourceInfo.GroupResource().Resource)
|
||||||
|
delete(oas.Paths.Paths, root+notificationsModels.TimeIntervalResourceInfo.GroupResource().Resource)
|
||||||
|
|
||||||
|
// The root API discovery list
|
||||||
|
sub := oas.Paths.Paths[root]
|
||||||
|
if sub != nil && sub.Get != nil {
|
||||||
|
sub.Get.Tags = []string{"API Discovery"} // sorts first in the list
|
||||||
|
}
|
||||||
|
return oas, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *NotificationsAPIBuilder) GetAuthorizer() authorizer.Authorizer {
|
||||||
return authorizer.AuthorizerFunc(
|
return authorizer.AuthorizerFunc(
|
||||||
func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
switch a.GetResource() {
|
switch a.GetResource() {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
model "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
model "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
func convertToK8sResources(orgID int64, intervals []definitions.MuteTimeInterval, namespacer request.NamespaceMapper, selector fields.Selector) (*model.TimeIntervalList, error) {
|
func convertToK8sResources(orgID int64, intervals []definitions.MuteTimeInterval, namespacer request.NamespaceMapper, selector fields.Selector) (*model.TimeIntervalList, error) {
|
||||||
@@ -77,6 +78,7 @@ func convertToDomainModel(interval *model.TimeInterval) (definitions.MuteTimeInt
|
|||||||
}
|
}
|
||||||
result.Version = interval.ResourceVersion
|
result.Version = interval.ResourceVersion
|
||||||
result.UID = interval.ObjectMeta.Name
|
result.UID = interval.ObjectMeta.Name
|
||||||
|
result.Provenance = definitions.Provenance(models.ProvenanceNone)
|
||||||
err = result.Validate()
|
err = result.Validate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return definitions.MuteTimeInterval{}, err
|
return definitions.MuteTimeInterval{}, err
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package timeinterval
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@@ -16,7 +15,6 @@ import (
|
|||||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ grafanarest.Storage = (*storage)(nil)
|
var _ grafanarest.Storage = (*storage)(nil)
|
||||||
@@ -38,24 +36,9 @@ func NewStorage(
|
|||||||
dualWriteBuilder grafanarest.DualWriteBuilder,
|
dualWriteBuilder grafanarest.DualWriteBuilder,
|
||||||
) (rest.Storage, error) {
|
) (rest.Storage, error) {
|
||||||
legacyStore := &legacyStorage{
|
legacyStore := &legacyStorage{
|
||||||
service: legacySvc,
|
service: legacySvc,
|
||||||
namespacer: namespacer,
|
namespacer: namespacer,
|
||||||
tableConverter: utils.NewTableConverter(
|
tableConverter: resourceInfo.TableConverter(),
|
||||||
resourceInfo.GroupResource(),
|
|
||||||
[]metav1.TableColumnDefinition{
|
|
||||||
{Name: "Name", Type: "string", Format: "name"},
|
|
||||||
// {Name: "Intervals", Type: "string", Format: "string", Description: "The display name"},
|
|
||||||
},
|
|
||||||
func(obj any) ([]interface{}, error) {
|
|
||||||
r, ok := obj.(*model.TimeInterval)
|
|
||||||
if ok {
|
|
||||||
return []interface{}{
|
|
||||||
r.Name,
|
|
||||||
// r.Spec, //TODO implement formatting for Spec, same as UI?
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("expected resource or info")
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
if optsGetter != nil && dualWriteBuilder != nil {
|
if optsGetter != nil && dualWriteBuilder != nil {
|
||||||
strategy := grafanaregistry.NewStrategy(scheme)
|
strategy := grafanaregistry.NewStrategy(scheme)
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package legacy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Templates setup.
|
||||||
|
var (
|
||||||
|
//go:embed *.sql
|
||||||
|
sqlTemplatesFS embed.FS
|
||||||
|
|
||||||
|
sqlTemplates = template.Must(template.New("sql").ParseFS(sqlTemplatesFS, `*.sql`))
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustTemplate(filename string) *template.Template {
|
||||||
|
if t := sqlTemplates.Lookup(filename); t != nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
panic(fmt.Sprintf("template file not found: %s", filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Templates.
|
||||||
|
var (
|
||||||
|
sqlQueryDashboards = mustTemplate("query_dashboards.sql")
|
||||||
|
)
|
||||||
|
|
||||||
|
type sqlQuery struct {
|
||||||
|
*sqltemplate.SQLTemplate
|
||||||
|
Query *DashboardQuery
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r sqlQuery) Validate() error {
|
||||||
|
return nil // TODO
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package legacy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed testdata/*
|
||||||
|
var testdataFS embed.FS
|
||||||
|
|
||||||
|
func testdata(t *testing.T, filename string) []byte {
|
||||||
|
t.Helper()
|
||||||
|
b, err := testdataFS.ReadFile(`testdata/` + filename)
|
||||||
|
if err != nil {
|
||||||
|
writeTestData(filename, "<empty>")
|
||||||
|
assert.Fail(t, "missing test file")
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeTestData(filename, value string) {
|
||||||
|
_ = os.WriteFile(filepath.Join("testdata", filename), []byte(value), 0777)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueries(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// Check each dialect
|
||||||
|
dialects := []sqltemplate.Dialect{
|
||||||
|
sqltemplate.MySQL,
|
||||||
|
sqltemplate.SQLite,
|
||||||
|
sqltemplate.PostgreSQL,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each template has one or more test cases, each identified with a
|
||||||
|
// descriptive name (e.g. "happy path", "error twiddling the frobb"). Each
|
||||||
|
// of them will test that for the same input data they must produce a result
|
||||||
|
// that will depend on the Dialect. Expected queries should be defined in
|
||||||
|
// separate files in the testdata directory. This improves the testing
|
||||||
|
// experience by separating test data from test code, since mixing both
|
||||||
|
// tends to make it more difficult to reason about what is being done,
|
||||||
|
// especially as we want testing code to scale and make it easy to add
|
||||||
|
// tests.
|
||||||
|
type (
|
||||||
|
testCase = struct {
|
||||||
|
Name string
|
||||||
|
|
||||||
|
// Data should be the struct passed to the template.
|
||||||
|
Data sqltemplate.SQLTemplateIface
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define tests cases. Most templates are trivial and testing that they
|
||||||
|
// generate correct code for a single Dialect is fine, since the one thing
|
||||||
|
// that always changes is how SQL placeholder arguments are passed (most
|
||||||
|
// Dialects use `?` while PostgreSQL uses `$1`, `$2`, etc.), and that is
|
||||||
|
// something that should be tested in the Dialect implementation instead of
|
||||||
|
// here. We will ask to have at least one test per SQL template, and we will
|
||||||
|
// lean to test MySQL. Templates containing branching (conditionals, loops,
|
||||||
|
// etc.) should be exercised at least once in each of their branches.
|
||||||
|
//
|
||||||
|
// NOTE: in the Data field, make sure to have pointers populated to simulate
|
||||||
|
// data is set as it would be in a real request. The data being correctly
|
||||||
|
// populated in each case should be tested in integration tests, where the
|
||||||
|
// data will actually flow to and from a real database. In this tests we
|
||||||
|
// only care about producing the correct SQL.
|
||||||
|
testCases := map[*template.Template][]*testCase{
|
||||||
|
sqlQueryDashboards: {
|
||||||
|
{
|
||||||
|
Name: "history_uid",
|
||||||
|
Data: &sqlQuery{
|
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||||
|
Query: &DashboardQuery{
|
||||||
|
OrgID: 2,
|
||||||
|
UID: "UUU",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "history_uid_at_version",
|
||||||
|
Data: &sqlQuery{
|
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||||
|
Query: &DashboardQuery{
|
||||||
|
OrgID: 2,
|
||||||
|
UID: "UUU",
|
||||||
|
Version: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "history_uid_second_page",
|
||||||
|
Data: &sqlQuery{
|
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||||
|
Query: &DashboardQuery{
|
||||||
|
OrgID: 2,
|
||||||
|
UID: "UUU",
|
||||||
|
LastID: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "dashboard",
|
||||||
|
Data: &sqlQuery{
|
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||||
|
Query: &DashboardQuery{
|
||||||
|
OrgID: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "dashboard_next_page",
|
||||||
|
Data: &sqlQuery{
|
||||||
|
SQLTemplate: new(sqltemplate.SQLTemplate),
|
||||||
|
Query: &DashboardQuery{
|
||||||
|
OrgID: 2,
|
||||||
|
LastID: 22,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute test cases
|
||||||
|
for tmpl, tcs := range testCases {
|
||||||
|
t.Run(tmpl.Name(), func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, dialect := range dialects {
|
||||||
|
filename := dialect.DialectName() + "__" + tc.Name + ".sql"
|
||||||
|
t.Run(filename, func(t *testing.T) {
|
||||||
|
// not parallel because we're sharing tc.Data, not
|
||||||
|
// worth it deep cloning
|
||||||
|
|
||||||
|
expectedQuery := string(testdata(t, filename))
|
||||||
|
//expectedQuery := sqltemplate.FormatSQL(rawQuery)
|
||||||
|
|
||||||
|
tc.Data.SetDialect(dialect)
|
||||||
|
err := tc.Data.Validate()
|
||||||
|
require.NoError(t, err)
|
||||||
|
got, err := sqltemplate.Execute(tmpl, tc.Data)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
got = sqltemplate.RemoveEmptyLines(got)
|
||||||
|
if diff := cmp.Diff(expectedQuery, got); diff != "" {
|
||||||
|
writeTestData(filename, got)
|
||||||
|
t.Errorf("%s: %s", tc.Name, diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
SELECT
|
||||||
|
dashboard.org_id, dashboard.id,
|
||||||
|
dashboard.uid, dashboard.folder_uid,
|
||||||
|
dashboard.deleted, plugin_id,
|
||||||
|
dashboard_provisioning.name as origin_name,
|
||||||
|
dashboard_provisioning.external_id as origin_path,
|
||||||
|
dashboard_provisioning.check_sum as origin_key,
|
||||||
|
dashboard_provisioning.updated as origin_ts,
|
||||||
|
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||||
|
{{ if .Query.UseHistoryTable }}
|
||||||
|
dashboard_version.created, updated_user.uid as updated_by,updated_user.id as created_by_id,
|
||||||
|
dashboard_version.version, dashboard_version.message, dashboard_version.data
|
||||||
|
{{ else }}
|
||||||
|
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||||
|
dashboard.version, '' as message, dashboard.data
|
||||||
|
{{ end }}
|
||||||
|
FROM dashboard
|
||||||
|
{{ if .Query.UseHistoryTable }}
|
||||||
|
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
|
||||||
|
{{ end }}
|
||||||
|
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||||
|
LEFT OUTER JOIN {{ .Ident "user" }} AS created_user ON dashboard.created_by = created_user.id
|
||||||
|
LEFT OUTER JOIN {{ .Ident "user" }} AS updated_user ON dashboard.updated_by = updated_user.id
|
||||||
|
WHERE dashboard.is_folder = false
|
||||||
|
AND dashboard.org_id = {{ .Arg .Query.OrgID }}
|
||||||
|
{{ if .Query.UseHistoryTable }}
|
||||||
|
{{ if .Query.Version }}
|
||||||
|
AND dashboard_version.version = {{ .Arg .Query.Version }}
|
||||||
|
{{ else if .Query.LastID }}
|
||||||
|
AND dashboard_version.version < {{ .Arg .Query.LastID }}
|
||||||
|
{{ end }}
|
||||||
|
ORDER BY dashboard_version.version DESC
|
||||||
|
{{ else }}
|
||||||
|
{{ if .Query.UID }}
|
||||||
|
AND dashboard.uid = {{ .Arg .Query.UID }}
|
||||||
|
{{ else if .Query.LastID }}
|
||||||
|
AND dashboard.id > {{ .Arg .Query.LastID }}
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Query.GetTrash }}
|
||||||
|
AND dashboard.deleted IS NOT NULL
|
||||||
|
{{ else if .Query.LastID }}
|
||||||
|
AND dashboard.deleted IS NULL
|
||||||
|
{{ end }}
|
||||||
|
ORDER BY dashboard.id DESC
|
||||||
|
{{ end }}
|
||||||
@@ -9,9 +9,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/utils/ptr"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
dashboardsV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
dashboardsV0 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||||
@@ -21,8 +18,12 @@ import (
|
|||||||
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
|
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||||
|
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore/session"
|
"github.com/grafana/grafana/pkg/services/sqlstore/session"
|
||||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/utils/ptr"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -46,10 +47,12 @@ type dashboardRow struct {
|
|||||||
|
|
||||||
type dashboardSqlAccess struct {
|
type dashboardSqlAccess struct {
|
||||||
sql db.DB
|
sql db.DB
|
||||||
|
dialect sqltemplate.Dialect
|
||||||
sess *session.SessionDB
|
sess *session.SessionDB
|
||||||
namespacer request.NamespaceMapper
|
namespacer request.NamespaceMapper
|
||||||
dashStore dashboards.Store
|
dashStore dashboards.Store
|
||||||
provisioning provisioning.ProvisioningService
|
provisioning provisioning.ProvisioningService
|
||||||
|
currentRV func(ctx context.Context) (int64, error)
|
||||||
|
|
||||||
// Typically one... the server wrapper
|
// Typically one... the server wrapper
|
||||||
subscribers []chan *resource.WrittenEvent
|
subscribers []chan *resource.WrittenEvent
|
||||||
@@ -61,131 +64,82 @@ func NewDashboardAccess(sql db.DB,
|
|||||||
dashStore dashboards.Store,
|
dashStore dashboards.Store,
|
||||||
provisioning provisioning.ProvisioningService,
|
provisioning provisioning.ProvisioningService,
|
||||||
) DashboardAccess {
|
) DashboardAccess {
|
||||||
|
dialect := sqltemplate.DialectForDriver(string(sql.GetDBType()))
|
||||||
|
if dialect == nil {
|
||||||
|
// panic?
|
||||||
|
// fmt.Errorf("no dialect for driver %q", driverName)
|
||||||
|
fmt.Printf("ERROR: NO DIALECT")
|
||||||
|
}
|
||||||
|
|
||||||
|
sess := sql.GetSqlxSession()
|
||||||
|
currentRV := func(ctx context.Context) (int64, error) {
|
||||||
|
t := time.Now()
|
||||||
|
max := ""
|
||||||
|
err := sess.Get(ctx, &max, "SELECT MAX(updated) FROM dashboard")
|
||||||
|
if err == nil && max != "" {
|
||||||
|
t, _ = time.Parse(time.DateTime, max) // ignore null errors
|
||||||
|
}
|
||||||
|
return t.UnixMilli(), nil
|
||||||
|
}
|
||||||
|
if sql.GetDBType() == migrator.Postgres {
|
||||||
|
currentRV = func(ctx context.Context) (int64, error) {
|
||||||
|
max := time.Now()
|
||||||
|
_ = sess.Get(ctx, &max, "SELECT MAX(updated) FROM dashboard")
|
||||||
|
return max.UnixMilli(), nil
|
||||||
|
}
|
||||||
|
} else if sql.GetDBType() == migrator.MySQL {
|
||||||
|
currentRV = func(ctx context.Context) (int64, error) {
|
||||||
|
max := time.Now().UnixMilli()
|
||||||
|
_ = sess.Get(ctx, &max, "SELECT UNIX_TIMESTAMP(MAX(updated)) FROM dashboard;")
|
||||||
|
return max, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return &dashboardSqlAccess{
|
return &dashboardSqlAccess{
|
||||||
sql: sql,
|
sql: sql,
|
||||||
sess: sql.GetSqlxSession(),
|
sess: sess,
|
||||||
|
dialect: dialect,
|
||||||
namespacer: namespacer,
|
namespacer: namespacer,
|
||||||
dashStore: dashStore,
|
dashStore: dashStore,
|
||||||
provisioning: provisioning,
|
provisioning: provisioning,
|
||||||
|
currentRV: currentRV,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *dashboardSqlAccess) currentRV(ctx context.Context) (int64, error) {
|
|
||||||
t := time.Now()
|
|
||||||
max := ""
|
|
||||||
err := a.sess.Get(ctx, &max, "SELECT MAX(updated) FROM dashboard")
|
|
||||||
if err == nil && max != "" {
|
|
||||||
t, err = time.Parse(time.DateTime, max)
|
|
||||||
}
|
|
||||||
return t.UnixMilli(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
const selector = `SELECT
|
|
||||||
dashboard.org_id, dashboard.id,
|
|
||||||
dashboard.uid, dashboard.folder_uid,
|
|
||||||
dashboard.created,CreatedUSER.uid as created_by,
|
|
||||||
dashboard.updated,UpdatedUSER.uid as updated_by,
|
|
||||||
dashboard.deleted, plugin_id,
|
|
||||||
dashboard_provisioning.name as origin_name,
|
|
||||||
dashboard_provisioning.external_id as origin_path,
|
|
||||||
dashboard_provisioning.check_sum as origin_key,
|
|
||||||
dashboard_provisioning.updated as origin_ts,
|
|
||||||
dashboard.version, '', dashboard.data
|
|
||||||
FROM dashboard
|
|
||||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
|
||||||
LEFT OUTER JOIN user AS CreatedUSER ON dashboard.created_by = CreatedUSER.id
|
|
||||||
LEFT OUTER JOIN user AS UpdatedUSER ON dashboard.updated_by = UpdatedUSER.id
|
|
||||||
WHERE dashboard.is_folder = false`
|
|
||||||
|
|
||||||
const history = `SELECT
|
|
||||||
dashboard.org_id, dashboard.id,
|
|
||||||
dashboard.uid, dashboard.folder_uid,
|
|
||||||
dashboard.created,CreatedUSER.uid as created_by,
|
|
||||||
dashboard_version.created,UpdatedUSER.uid as updated_by,
|
|
||||||
NULL, plugin_id,
|
|
||||||
dashboard_provisioning.name as origin_name,
|
|
||||||
dashboard_provisioning.external_id as origin_path,
|
|
||||||
dashboard_provisioning.check_sum as origin_key,
|
|
||||||
dashboard_provisioning.updated as origin_ts,
|
|
||||||
dashboard_version.version, dashboard_version.message, dashboard_version.data
|
|
||||||
FROM dashboard
|
|
||||||
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
|
||||||
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
|
|
||||||
LEFT OUTER JOIN user AS CreatedUSER ON dashboard.created_by = CreatedUSER.id
|
|
||||||
LEFT OUTER JOIN user AS UpdatedUSER ON dashboard_version.created_by = UpdatedUSER.id
|
|
||||||
WHERE dashboard.is_folder = false`
|
|
||||||
|
|
||||||
func (a *dashboardSqlAccess) getRows(ctx context.Context, query *DashboardQuery) (*rowsWrapper, error) {
|
func (a *dashboardSqlAccess) getRows(ctx context.Context, query *DashboardQuery) (*rowsWrapper, error) {
|
||||||
if len(query.Labels) > 0 {
|
if len(query.Labels) > 0 {
|
||||||
return nil, fmt.Errorf("labels not yet supported")
|
return nil, fmt.Errorf("labels not yet supported")
|
||||||
// if query.Requirements.Folder != nil {
|
// if query.Requirements.Folder != nil {
|
||||||
// args = append(args, *query.Requirements.Folder)
|
// args = append(args, *query.Requirements.Folder)
|
||||||
// sqlcmd = fmt.Sprintf("%s AND dashboard.folder_uid=$%d", sqlcmd, len(args))
|
// sqlcmd = fmt.Sprintf("%s AND dashboard.folder_uid=?$%d", sqlcmd, len(args))
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
var sqlcmd string
|
req := sqlQuery{
|
||||||
args := []any{query.OrgID}
|
SQLTemplate: sqltemplate.New(a.dialect),
|
||||||
|
Query: query,
|
||||||
if query.GetHistory || query.Version > 0 {
|
|
||||||
if query.GetTrash {
|
|
||||||
return nil, fmt.Errorf("trash not included in history table")
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlcmd = fmt.Sprintf("%s AND dashboard.org_id=$%d\n ", history, len(args))
|
|
||||||
|
|
||||||
if query.UID == "" {
|
|
||||||
return nil, fmt.Errorf("history query must have a UID")
|
|
||||||
}
|
|
||||||
|
|
||||||
args = append(args, query.UID)
|
|
||||||
sqlcmd = fmt.Sprintf("%s AND dashboard.uid=$%d", sqlcmd, len(args))
|
|
||||||
|
|
||||||
if query.Version > 0 {
|
|
||||||
args = append(args, query.Version)
|
|
||||||
sqlcmd = fmt.Sprintf("%s AND dashboard_version.version=$%d", sqlcmd, len(args))
|
|
||||||
} else if query.LastID > 0 {
|
|
||||||
args = append(args, query.LastID)
|
|
||||||
sqlcmd = fmt.Sprintf("%s AND dashboard_version.version<$%d", sqlcmd, len(args))
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlcmd = fmt.Sprintf("%s\n ORDER BY dashboard_version.version desc", sqlcmd)
|
|
||||||
} else {
|
|
||||||
sqlcmd = fmt.Sprintf("%s AND dashboard.org_id=$%d\n ", selector, len(args))
|
|
||||||
|
|
||||||
if query.UID != "" {
|
|
||||||
args = append(args, query.UID)
|
|
||||||
sqlcmd = fmt.Sprintf("%s AND dashboard.uid=$%d", sqlcmd, len(args))
|
|
||||||
} else if query.LastID > 0 {
|
|
||||||
args = append(args, query.LastID)
|
|
||||||
sqlcmd = fmt.Sprintf("%s AND dashboard.id>$%d", sqlcmd, len(args))
|
|
||||||
}
|
|
||||||
if query.GetTrash {
|
|
||||||
sqlcmd = sqlcmd + " AND dashboard.deleted IS NOT NULL"
|
|
||||||
} else {
|
|
||||||
sqlcmd = sqlcmd + " AND dashboard.deleted IS NULL"
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlcmd = fmt.Sprintf("%s\n ORDER BY dashboard.id asc", sqlcmd)
|
|
||||||
}
|
}
|
||||||
// fmt.Printf("%s // %v\n", sqlcmd, args)
|
|
||||||
|
|
||||||
rows, err := a.doQuery(ctx, sqlcmd, args...)
|
tmpl := sqlQueryDashboards
|
||||||
|
if query.UseHistoryTable() && query.GetTrash {
|
||||||
|
return nil, fmt.Errorf("trash not included in history table")
|
||||||
|
}
|
||||||
|
|
||||||
|
rawQuery, err := sqltemplate.Execute(tmpl, req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("execute template %q: %w", tmpl.Name(), err)
|
||||||
|
}
|
||||||
|
q := rawQuery
|
||||||
|
// q = sqltemplate.RemoveEmptyLines(rawQuery)
|
||||||
|
// fmt.Printf(">>%s [%+v]", q, req.GetArgs())
|
||||||
|
|
||||||
|
rows, err := a.sess.Query(ctx, q, req.GetArgs()...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if rows != nil {
|
if rows != nil {
|
||||||
_ = rows.Close()
|
_ = rows.Close()
|
||||||
}
|
}
|
||||||
rows = nil
|
rows = nil
|
||||||
}
|
}
|
||||||
return rows, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *dashboardSqlAccess) doQuery(ctx context.Context, query string, args ...any) (*rowsWrapper, error) {
|
|
||||||
_, err := identity.GetRequester(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rows, err := a.sess.Query(ctx, query, args...)
|
|
||||||
return &rowsWrapper{
|
return &rowsWrapper{
|
||||||
rows: rows,
|
rows: rows,
|
||||||
a: a,
|
a: a,
|
||||||
@@ -211,6 +165,9 @@ type rowsWrapper struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *rowsWrapper) Close() error {
|
func (r *rowsWrapper) Close() error {
|
||||||
|
if r.rows == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
return r.rows.Close()
|
return r.rows.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,10 +248,12 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
|
|||||||
var folder_uid sql.NullString
|
var folder_uid sql.NullString
|
||||||
var updated time.Time
|
var updated time.Time
|
||||||
var updatedBy sql.NullString
|
var updatedBy sql.NullString
|
||||||
|
var updatedByID sql.NullInt64
|
||||||
var deleted sql.NullTime
|
var deleted sql.NullTime
|
||||||
|
|
||||||
var created time.Time
|
var created time.Time
|
||||||
var createdBy sql.NullString
|
var createdBy sql.NullString
|
||||||
|
var createdByID sql.NullInt64
|
||||||
var message sql.NullString
|
var message sql.NullString
|
||||||
|
|
||||||
var plugin_id string
|
var plugin_id string
|
||||||
@@ -306,10 +265,10 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
|
|||||||
var version int64
|
var version int64
|
||||||
|
|
||||||
err := rows.Scan(&orgId, &dashboard_id, &dash.Name, &folder_uid,
|
err := rows.Scan(&orgId, &dashboard_id, &dash.Name, &folder_uid,
|
||||||
&created, &createdBy,
|
|
||||||
&updated, &updatedBy,
|
|
||||||
&deleted, &plugin_id,
|
&deleted, &plugin_id,
|
||||||
&origin_name, &origin_path, &origin_hash, &origin_ts,
|
&origin_name, &origin_path, &origin_hash, &origin_ts,
|
||||||
|
&created, &createdBy, &createdByID,
|
||||||
|
&updated, &updatedBy, &updatedByID,
|
||||||
&version, &message, &data,
|
&version, &message, &data,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -325,8 +284,8 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
meta.SetUpdatedTimestamp(&updated)
|
meta.SetUpdatedTimestamp(&updated)
|
||||||
meta.SetCreatedBy(getUserID(createdBy))
|
meta.SetCreatedBy(getUserID(createdBy, createdByID))
|
||||||
meta.SetUpdatedBy(getUserID(updatedBy))
|
meta.SetUpdatedBy(getUserID(updatedBy, updatedByID))
|
||||||
|
|
||||||
if deleted.Valid {
|
if deleted.Valid {
|
||||||
meta.SetDeletionTimestamp(ptr.To(metav1.NewTime(deleted.Time)))
|
meta.SetDeletionTimestamp(ptr.To(metav1.NewTime(deleted.Time)))
|
||||||
@@ -377,11 +336,14 @@ func (a *dashboardSqlAccess) scanRow(rows *sql.Rows) (*dashboardRow, error) {
|
|||||||
return row, err
|
return row, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserID(v sql.NullString) string {
|
func getUserID(v sql.NullString, id sql.NullInt64) string {
|
||||||
if v.String == "" {
|
if v.Valid && v.String != "" {
|
||||||
|
return identity.NewTypedIDString(identity.TypeUser, v.String).String()
|
||||||
|
}
|
||||||
|
if id.Valid && id.Int64 == -1 {
|
||||||
return identity.NewTypedIDString(identity.TypeProvisioning, "").String()
|
return identity.NewTypedIDString(identity.TypeProvisioning, "").String()
|
||||||
}
|
}
|
||||||
return identity.NewTypedIDString(identity.TypeUser, v.String).String()
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteDashboard implements DashboardAccess.
|
// DeleteDashboard implements DashboardAccess.
|
||||||
|
|||||||
@@ -147,13 +147,13 @@ func (a *dashboardSqlAccess) ReadResource(ctx context.Context, req *resource.Rea
|
|||||||
rsp.Error = &resource.ErrorResult{
|
rsp.Error = &resource.ErrorResult{
|
||||||
Code: http.StatusNotFound,
|
Code: http.StatusNotFound,
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
rsp.Value, err = json.Marshal(dash)
|
||||||
|
if err != nil {
|
||||||
|
rsp.Error = resource.AsErrorResult(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rsp.ResourceVersion = rv
|
rsp.ResourceVersion = rv
|
||||||
rsp.Value, err = json.Marshal(dash)
|
|
||||||
if err != nil {
|
|
||||||
rsp.Error = resource.AsErrorResult(err)
|
|
||||||
}
|
|
||||||
return rsp
|
return rsp
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,11 +177,10 @@ func (a *dashboardSqlAccess) ListIterator(ctx context.Context, req *resource.Lis
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &DashboardQuery{
|
query := &DashboardQuery{
|
||||||
OrgID: info.OrgID,
|
OrgID: info.OrgID,
|
||||||
Limit: int(req.Limit),
|
Limit: int(req.Limit),
|
||||||
MaxBytes: 2 * 1024 * 1024, // 2MB,
|
LastID: token.id,
|
||||||
LastID: token.id,
|
Labels: req.Options.Labels,
|
||||||
Labels: req.Options.Labels,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
listRV, err := a.currentRV(ctx)
|
listRV, err := a.currentRV(ctx)
|
||||||
@@ -194,7 +193,7 @@ func (a *dashboardSqlAccess) ListIterator(ctx context.Context, req *resource.Lis
|
|||||||
_ = rows.Close()
|
_ = rows.Close()
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err == nil {
|
||||||
err = cb(rows)
|
err = cb(rows)
|
||||||
}
|
}
|
||||||
return listRV, err
|
return listRV, err
|
||||||
@@ -253,13 +252,15 @@ func (a *dashboardSqlAccess) History(ctx context.Context, req *resource.HistoryR
|
|||||||
if token.orgId > 0 && token.orgId != info.OrgID {
|
if token.orgId > 0 && token.orgId != info.OrgID {
|
||||||
return nil, fmt.Errorf("token and orgID mismatch")
|
return nil, fmt.Errorf("token and orgID mismatch")
|
||||||
}
|
}
|
||||||
|
limit := int(req.Limit)
|
||||||
|
if limit < 1 {
|
||||||
|
limit = 15
|
||||||
|
}
|
||||||
query := &DashboardQuery{
|
query := &DashboardQuery{
|
||||||
OrgID: info.OrgID,
|
OrgID: info.OrgID,
|
||||||
Limit: int(req.Limit),
|
Limit: limit + 1,
|
||||||
MaxBytes: 2 * 1024 * 1024, // 2MB,
|
LastID: token.id,
|
||||||
LastID: token.id,
|
UID: req.Key.Name,
|
||||||
UID: req.Key.Name,
|
|
||||||
}
|
}
|
||||||
if req.ShowDeleted {
|
if req.ShowDeleted {
|
||||||
query.GetTrash = true
|
query.GetTrash = true
|
||||||
@@ -273,7 +274,6 @@ func (a *dashboardSqlAccess) History(ctx context.Context, req *resource.HistoryR
|
|||||||
}
|
}
|
||||||
defer func() { _ = rows.Close() }()
|
defer func() { _ = rows.Close() }()
|
||||||
|
|
||||||
totalSize := 0
|
|
||||||
list := &resource.HistoryResponse{}
|
list := &resource.HistoryResponse{}
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
if rows.err != nil || rows.row == nil {
|
if rows.err != nil || rows.row == nil {
|
||||||
@@ -291,8 +291,7 @@ func (a *dashboardSqlAccess) History(ctx context.Context, req *resource.HistoryR
|
|||||||
return list, err
|
return list, err
|
||||||
}
|
}
|
||||||
|
|
||||||
totalSize += len(rows.Value())
|
if len(list.Items) >= limit {
|
||||||
if len(list.Items) > 0 && (totalSize > query.MaxBytes || len(list.Items) >= query.Limit) {
|
|
||||||
// if query.Requirements.Folder != nil {
|
// if query.Requirements.Folder != nil {
|
||||||
// row.token.folder = *query.Requirements.Folder
|
// row.token.folder = *query.Requirements.Folder
|
||||||
// }
|
// }
|
||||||
|
|||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
SELECT
|
||||||
|
dashboard.org_id, dashboard.id,
|
||||||
|
dashboard.uid, dashboard.folder_uid,
|
||||||
|
dashboard.deleted, plugin_id,
|
||||||
|
dashboard_provisioning.name as origin_name,
|
||||||
|
dashboard_provisioning.external_id as origin_path,
|
||||||
|
dashboard_provisioning.check_sum as origin_key,
|
||||||
|
dashboard_provisioning.updated as origin_ts,
|
||||||
|
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||||
|
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||||
|
dashboard.version, '' as message, dashboard.data
|
||||||
|
FROM dashboard
|
||||||
|
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||||
|
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
|
||||||
|
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
|
||||||
|
WHERE dashboard.is_folder = false
|
||||||
|
AND dashboard.org_id = ?
|
||||||
|
ORDER BY dashboard.id DESC
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
SELECT
|
||||||
|
dashboard.org_id, dashboard.id,
|
||||||
|
dashboard.uid, dashboard.folder_uid,
|
||||||
|
dashboard.deleted, plugin_id,
|
||||||
|
dashboard_provisioning.name as origin_name,
|
||||||
|
dashboard_provisioning.external_id as origin_path,
|
||||||
|
dashboard_provisioning.check_sum as origin_key,
|
||||||
|
dashboard_provisioning.updated as origin_ts,
|
||||||
|
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||||
|
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||||
|
dashboard.version, '' as message, dashboard.data
|
||||||
|
FROM dashboard
|
||||||
|
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||||
|
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
|
||||||
|
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
|
||||||
|
WHERE dashboard.is_folder = false
|
||||||
|
AND dashboard.org_id = ?
|
||||||
|
AND dashboard.id > ?
|
||||||
|
AND dashboard.deleted IS NULL
|
||||||
|
ORDER BY dashboard.id DESC
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
SELECT
|
||||||
|
dashboard.org_id, dashboard.id,
|
||||||
|
dashboard.uid, dashboard.folder_uid,
|
||||||
|
dashboard.deleted, plugin_id,
|
||||||
|
dashboard_provisioning.name as origin_name,
|
||||||
|
dashboard_provisioning.external_id as origin_path,
|
||||||
|
dashboard_provisioning.check_sum as origin_key,
|
||||||
|
dashboard_provisioning.updated as origin_ts,
|
||||||
|
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||||
|
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||||
|
dashboard.version, '' as message, dashboard.data
|
||||||
|
FROM dashboard
|
||||||
|
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||||
|
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
|
||||||
|
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
|
||||||
|
WHERE dashboard.is_folder = false
|
||||||
|
AND dashboard.org_id = ?
|
||||||
|
AND dashboard.uid = ?
|
||||||
|
ORDER BY dashboard.id DESC
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
SELECT
|
||||||
|
dashboard.org_id, dashboard.id,
|
||||||
|
dashboard.uid, dashboard.folder_uid,
|
||||||
|
dashboard.deleted, plugin_id,
|
||||||
|
dashboard_provisioning.name as origin_name,
|
||||||
|
dashboard_provisioning.external_id as origin_path,
|
||||||
|
dashboard_provisioning.check_sum as origin_key,
|
||||||
|
dashboard_provisioning.updated as origin_ts,
|
||||||
|
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||||
|
dashboard_version.created, updated_user.uid as updated_by,updated_user.id as created_by_id,
|
||||||
|
dashboard_version.version, dashboard_version.message, dashboard_version.data
|
||||||
|
FROM dashboard
|
||||||
|
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
|
||||||
|
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||||
|
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
|
||||||
|
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
|
||||||
|
WHERE dashboard.is_folder = false
|
||||||
|
AND dashboard.org_id = ?
|
||||||
|
AND dashboard_version.version = ?
|
||||||
|
ORDER BY dashboard_version.version DESC
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
SELECT
|
||||||
|
dashboard.org_id, dashboard.id,
|
||||||
|
dashboard.uid, dashboard.folder_uid,
|
||||||
|
dashboard.deleted, plugin_id,
|
||||||
|
dashboard_provisioning.name as origin_name,
|
||||||
|
dashboard_provisioning.external_id as origin_path,
|
||||||
|
dashboard_provisioning.check_sum as origin_key,
|
||||||
|
dashboard_provisioning.updated as origin_ts,
|
||||||
|
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||||
|
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||||
|
dashboard.version, '' as message, dashboard.data
|
||||||
|
FROM dashboard
|
||||||
|
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||||
|
LEFT OUTER JOIN `user` AS created_user ON dashboard.created_by = created_user.id
|
||||||
|
LEFT OUTER JOIN `user` AS updated_user ON dashboard.updated_by = updated_user.id
|
||||||
|
WHERE dashboard.is_folder = false
|
||||||
|
AND dashboard.org_id = ?
|
||||||
|
AND dashboard.uid = ?
|
||||||
|
AND dashboard.deleted IS NULL
|
||||||
|
ORDER BY dashboard.id DESC
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
SELECT
|
||||||
|
dashboard.org_id, dashboard.id,
|
||||||
|
dashboard.uid, dashboard.folder_uid,
|
||||||
|
dashboard.deleted, plugin_id,
|
||||||
|
dashboard_provisioning.name as origin_name,
|
||||||
|
dashboard_provisioning.external_id as origin_path,
|
||||||
|
dashboard_provisioning.check_sum as origin_key,
|
||||||
|
dashboard_provisioning.updated as origin_ts,
|
||||||
|
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||||
|
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||||
|
dashboard.version, '' as message, dashboard.data
|
||||||
|
FROM dashboard
|
||||||
|
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||||
|
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||||
|
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||||
|
WHERE dashboard.is_folder = false
|
||||||
|
AND dashboard.org_id = $1
|
||||||
|
ORDER BY dashboard.id DESC
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
SELECT
|
||||||
|
dashboard.org_id, dashboard.id,
|
||||||
|
dashboard.uid, dashboard.folder_uid,
|
||||||
|
dashboard.deleted, plugin_id,
|
||||||
|
dashboard_provisioning.name as origin_name,
|
||||||
|
dashboard_provisioning.external_id as origin_path,
|
||||||
|
dashboard_provisioning.check_sum as origin_key,
|
||||||
|
dashboard_provisioning.updated as origin_ts,
|
||||||
|
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||||
|
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||||
|
dashboard.version, '' as message, dashboard.data
|
||||||
|
FROM dashboard
|
||||||
|
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||||
|
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||||
|
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||||
|
WHERE dashboard.is_folder = false
|
||||||
|
AND dashboard.org_id = $1
|
||||||
|
AND dashboard.id > $2
|
||||||
|
AND dashboard.deleted IS NULL
|
||||||
|
ORDER BY dashboard.id DESC
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
SELECT
|
||||||
|
dashboard.org_id, dashboard.id,
|
||||||
|
dashboard.uid, dashboard.folder_uid,
|
||||||
|
dashboard.deleted, plugin_id,
|
||||||
|
dashboard_provisioning.name as origin_name,
|
||||||
|
dashboard_provisioning.external_id as origin_path,
|
||||||
|
dashboard_provisioning.check_sum as origin_key,
|
||||||
|
dashboard_provisioning.updated as origin_ts,
|
||||||
|
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||||
|
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||||
|
dashboard.version, '' as message, dashboard.data
|
||||||
|
FROM dashboard
|
||||||
|
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||||
|
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||||
|
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||||
|
WHERE dashboard.is_folder = false
|
||||||
|
AND dashboard.org_id = $1
|
||||||
|
AND dashboard.uid = $2
|
||||||
|
ORDER BY dashboard.id DESC
|
||||||
Vendored
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
SELECT
|
||||||
|
dashboard.org_id, dashboard.id,
|
||||||
|
dashboard.uid, dashboard.folder_uid,
|
||||||
|
dashboard.deleted, plugin_id,
|
||||||
|
dashboard_provisioning.name as origin_name,
|
||||||
|
dashboard_provisioning.external_id as origin_path,
|
||||||
|
dashboard_provisioning.check_sum as origin_key,
|
||||||
|
dashboard_provisioning.updated as origin_ts,
|
||||||
|
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||||
|
dashboard_version.created, updated_user.uid as updated_by,updated_user.id as created_by_id,
|
||||||
|
dashboard_version.version, dashboard_version.message, dashboard_version.data
|
||||||
|
FROM dashboard
|
||||||
|
LEFT OUTER JOIN dashboard_version ON dashboard.id = dashboard_version.dashboard_id
|
||||||
|
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||||
|
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||||
|
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||||
|
WHERE dashboard.is_folder = false
|
||||||
|
AND dashboard.org_id = $1
|
||||||
|
AND dashboard_version.version = $2
|
||||||
|
ORDER BY dashboard_version.version DESC
|
||||||
Vendored
Executable
+20
@@ -0,0 +1,20 @@
|
|||||||
|
SELECT
|
||||||
|
dashboard.org_id, dashboard.id,
|
||||||
|
dashboard.uid, dashboard.folder_uid,
|
||||||
|
dashboard.deleted, plugin_id,
|
||||||
|
dashboard_provisioning.name as origin_name,
|
||||||
|
dashboard_provisioning.external_id as origin_path,
|
||||||
|
dashboard_provisioning.check_sum as origin_key,
|
||||||
|
dashboard_provisioning.updated as origin_ts,
|
||||||
|
dashboard.created, created_user.uid as created_by, dashboard.created_by as created_by_id,
|
||||||
|
dashboard.updated, updated_user.uid as updated_by, dashboard.updated_by as updated_by_id,
|
||||||
|
dashboard.version, '' as message, dashboard.data
|
||||||
|
FROM dashboard
|
||||||
|
LEFT OUTER JOIN dashboard_provisioning ON dashboard.id = dashboard_provisioning.dashboard_id
|
||||||
|
LEFT OUTER JOIN "user" AS created_user ON dashboard.created_by = created_user.id
|
||||||
|
LEFT OUTER JOIN "user" AS updated_user ON dashboard.updated_by = updated_user.id
|
||||||
|
WHERE dashboard.is_folder = false
|
||||||
|
AND dashboard.org_id = $1
|
||||||
|
AND dashboard.uid = $2
|
||||||
|
AND dashboard.deleted IS NULL
|
||||||
|
ORDER BY dashboard.id DESC
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user