Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 37a1a45194 | |||
| 5dbbe8164b | |||
| d1064da4cd | |||
| b57b8d4359 | |||
| 5219ccddb6 | |||
| c95e3da2d5 | |||
| 43d9fbc056 | |||
| 7b80c44ac7 | |||
| 8cb33642ba | |||
| 98f271f345 | |||
| 60c4fab063 | |||
| 283ad15e84 | |||
| ce8663ac24 | |||
| 5dd9a14903 | |||
| 68bf19d840 | |||
| 220c29de89 | |||
| 91ab753368 | |||
| 250ca7985f | |||
| b57ed32484 | |||
| d0217588a3 | |||
| ce9ab6a89a | |||
| 8c8efd2494 | |||
| 69ccfd6bfc | |||
| 53aa5e8f7f | |||
| 69bf3068b3 | |||
| 1263a3d364 | |||
| 070dc2288e | |||
| e4b79e2fc8 | |||
| 0d1ec94548 | |||
| 23a51ec9c5 | |||
| 51dcdd3499 | |||
| 880bc23c85 | |||
| 6dc604c2ea | |||
| 77c500dc01 | |||
| bec4d225b3 | |||
| b91ca14f48 | |||
| 2aedbdb76f | |||
| 0d7f46c08a | |||
| 1b52718c23 | |||
| e61e406440 | |||
| 5cb4c311dc | |||
| 586410d8b5 | |||
| a0e894c6d8 | |||
| e4796b1de3 | |||
| 86a3aae204 | |||
| e0ad4eb7ed | |||
| f0c95a0a10 | |||
| 0b46123300 | |||
| 81b868ae91 | |||
| 2a6a48ac39 | |||
| f581a5a69b | |||
| 5ff18c0802 | |||
| 0c8c886930 | |||
| ba196958cd | |||
| 622c75af6d | |||
| e983aac141 | |||
| c42421e616 | |||
| e4ba1c1a6d | |||
| 909ed02218 | |||
| ad3763f04d | |||
| ca6ab973b4 | |||
| 9cd811b9e6 | |||
| 52cd096d92 | |||
| 98453fbcff | |||
| ccdafc3fb2 | |||
| 12abbd5a15 | |||
| 1c5caeb987 | |||
| 71a65e1f80 | |||
| ec12176220 | |||
| 0cf4f7c4de | |||
| b0785e506f | |||
| 5f8668b3aa |
@@ -14,6 +14,9 @@ outputs:
|
||||
frontend:
|
||||
description: Whether the frontend or self has changed in any way
|
||||
value: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }}
|
||||
frontend-packages:
|
||||
description: Whether any frontend packages have changed
|
||||
value: ${{ steps.changed-files.outputs.frontend_packages_any_changed || 'true' }}
|
||||
e2e:
|
||||
description: Whether the e2e tests or self have changed in any way
|
||||
value: ${{ steps.changed-files.outputs.e2e_any_changed == 'true' ||
|
||||
@@ -97,6 +100,12 @@ runs:
|
||||
- '.yarn/**'
|
||||
- 'apps/dashboard/pkg/migration/**'
|
||||
- '${{ inputs.self }}'
|
||||
frontend_packages:
|
||||
- '.github/actions/checkout/**'
|
||||
- '.github/actions/change-detection/**'
|
||||
- 'packages/**'
|
||||
- './scripts/validate-npm-packages.sh'
|
||||
- '${{ inputs.self }}'
|
||||
e2e:
|
||||
- 'e2e/**'
|
||||
- 'e2e-playwright/**'
|
||||
@@ -153,6 +162,8 @@ runs:
|
||||
echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}"
|
||||
echo "Frontend: ${{ steps.changed-files.outputs.frontend_any_changed || 'true' }}"
|
||||
echo " --> ${{ steps.changed-files.outputs.frontend_all_changed_files }}"
|
||||
echo "Frontend packages: ${{ steps.changed-files.outputs.frontend_packages_any_changed || 'true' }}"
|
||||
echo " --> ${{ steps.changed-files.outputs.frontend_packages_all_changed_files }}"
|
||||
echo "E2E: ${{ steps.changed-files.outputs.e2e_any_changed || 'true' }}"
|
||||
echo " --> ${{ steps.changed-files.outputs.e2e_all_changed_files }}"
|
||||
echo " --> ${{ steps.changed-files.outputs.backend_all_changed_files }}"
|
||||
|
||||
@@ -4,8 +4,8 @@ description: Sets up a node.js environment with presets for the Grafana reposito
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: 'yarn.lock'
|
||||
cache-dependency-path: 'yarn.lock'
|
||||
|
||||
@@ -17,6 +17,7 @@ jobs:
|
||||
outputs:
|
||||
changed: ${{ steps.detect-changes.outputs.frontend }}
|
||||
prettier: ${{ steps.detect-changes.outputs.frontend == 'true' || steps.detect-changes.outputs.docs == 'true' }}
|
||||
changed-frontend-packages: ${{ steps.detect-changes.outputs.frontend-packages }}
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
@@ -42,11 +43,8 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: 'yarn.lock'
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
- run: yarn install --immutable --check-cache
|
||||
- run: yarn run prettier:check
|
||||
- run: yarn run lint
|
||||
@@ -63,11 +61,8 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: 'yarn.lock'
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Enterprise
|
||||
uses: ./.github/actions/setup-enterprise
|
||||
with:
|
||||
@@ -89,11 +84,8 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: 'yarn.lock'
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
- run: yarn install --immutable --check-cache
|
||||
- run: yarn run typecheck
|
||||
lint-frontend-typecheck-enterprise:
|
||||
@@ -109,11 +101,8 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: 'yarn.lock'
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Enterprise
|
||||
uses: ./.github/actions/setup-enterprise
|
||||
with:
|
||||
@@ -133,11 +122,8 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: 'yarn.lock'
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
- run: yarn install --immutable --check-cache
|
||||
- name: Generate API clients
|
||||
run: |
|
||||
@@ -164,11 +150,8 @@ jobs:
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
cache-dependency-path: 'yarn.lock'
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Setup Enterprise
|
||||
uses: ./.github/actions/setup-enterprise
|
||||
with:
|
||||
@@ -187,3 +170,26 @@ jobs:
|
||||
echo "${uncommited_error_message}"
|
||||
exit 1
|
||||
fi
|
||||
lint-frontend-packed-packages:
|
||||
needs: detect-changes
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
if: github.event_name == 'pull_request' && needs.detect-changes.outputs.changed-frontend-packages == 'true'
|
||||
name: Verify packed frontend packages
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout build commit
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node
|
||||
- name: Install dependencies
|
||||
run: yarn install --immutable
|
||||
- name: Build and pack packages
|
||||
run: |
|
||||
yarn run packages:build
|
||||
yarn run packages:pack
|
||||
- name: Validate packages
|
||||
run: ./scripts/validate-npm-packages.sh
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
diff --git a/dist/builder-manager/index.js b/dist/builder-manager/index.js
|
||||
index 3d7f9b213dae1801bda62b31db31b9113e382ccd..212501c63d20146c29db63fb0f6300c6779eecb5 100644
|
||||
index ac8ac6a5f6a3b7852c4064e93dc9acd3201289e6..34a0a5a5c38dd7fe525c9ebd382a10a451d4d4f3 100644
|
||||
--- a/dist/builder-manager/index.js
|
||||
+++ b/dist/builder-manager/index.js
|
||||
@@ -1970,7 +1970,7 @@ var pa = /^\/($|\?)/, G, C, xt = /* @__PURE__ */ o(async (e) => {
|
||||
@@ -1974,7 +1974,7 @@ var pa = /^\/($|\?)/, G, C, xt = /* @__PURE__ */ o(async (e) => {
|
||||
bundle: !0,
|
||||
minify: !0,
|
||||
sourcemap: !1,
|
||||
@@ -135,7 +135,7 @@ i18n-extract-enterprise:
|
||||
@echo "Skipping i18n extract for Enterprise: not enabled"
|
||||
else
|
||||
i18n-extract-enterprise:
|
||||
@echo "Extracting i18n strings for Enterprise"
|
||||
@echo "Extracting i18n strings for Enterprise"
|
||||
cd public/locales/enterprise && yarn run i18next-cli extract --sync-primary
|
||||
endif
|
||||
|
||||
@@ -227,6 +227,10 @@ fix-cue:
|
||||
gen-jsonnet:
|
||||
go generate ./devenv/jsonnet
|
||||
|
||||
.PHONY: gen-themes
|
||||
gen-themes:
|
||||
go generate ./pkg/services/preference
|
||||
|
||||
.PHONY: update-workspace
|
||||
update-workspace: gen-go
|
||||
@echo "updating workspace"
|
||||
@@ -244,6 +248,7 @@ build-go-fast: ## Build all Go binaries without updating workspace.
|
||||
.PHONY: build-backend
|
||||
build-backend: ## Build Grafana backend.
|
||||
@echo "build backend"
|
||||
$(MAKE) gen-themes
|
||||
$(GO) run build.go $(GO_BUILD_FLAGS) build-backend
|
||||
|
||||
.PHONY: build-air
|
||||
|
||||
@@ -4,7 +4,7 @@ go 1.25.5
|
||||
|
||||
require (
|
||||
github.com/go-kit/log v0.2.1
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f
|
||||
github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4
|
||||
github.com/grafana/grafana-app-sdk v0.48.7
|
||||
github.com/grafana/grafana-app-sdk/logging v0.48.7
|
||||
|
||||
@@ -243,8 +243,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f h1:Br4SaUL3dnVopKKNhDavCLgehw60jdtl/sIxdfzmVts=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
|
||||
github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f h1:3bXOyht68qkfvD6Y8z8XoenFbytSSOIkr/s+AqRzj0o=
|
||||
github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f/go.mod h1:Ji0SfJChcwjgq8ljy6Y5CcYfHfAYKXjKYeysOoDS/6s=
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 h1:jSojuc7njleS3UOz223WDlXOinmuLAIPI0z2vtq8EgI=
|
||||
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4/go.mod h1:VahT+GtfQIM+o8ht2StR6J9g+Ef+C2Vokh5uuSmOD/4=
|
||||
github.com/grafana/grafana-app-sdk v0.48.7 h1:9mF7nqkqP0QUYYDlznoOt+GIyjzj45wGfUHB32u2ZMo=
|
||||
|
||||
+206
@@ -852,6 +852,194 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-7": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 7,
|
||||
"title": "Single Dashboard DS Query",
|
||||
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "datasource",
|
||||
"spec": {
|
||||
"panelId": 1,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "stat",
|
||||
"spec": {
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-8": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 8,
|
||||
"title": "Multiple Dashboard DS Queries",
|
||||
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "datasource",
|
||||
"spec": {
|
||||
"panelId": 1,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "datasource",
|
||||
"spec": {
|
||||
"panelId": 2,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"refId": "B",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "datasource",
|
||||
"spec": {
|
||||
"panelId": 3,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"refId": "C",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "stat",
|
||||
"spec": {
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
@@ -914,6 +1102,24 @@
|
||||
"name": "panel-6"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AutoGridLayoutItem",
|
||||
"spec": {
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-7"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AutoGridLayoutItem",
|
||||
"spec": {
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-8"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+220
@@ -879,6 +879,200 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-7": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 7,
|
||||
"title": "Single Dashboard DS Query",
|
||||
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "datasource",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "-- Dashboard --"
|
||||
},
|
||||
"spec": {
|
||||
"panelId": 1,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "VizConfig",
|
||||
"group": "stat",
|
||||
"version": "12.1.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-8": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 8,
|
||||
"title": "Multiple Dashboard DS Queries",
|
||||
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "datasource",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "-- Dashboard --"
|
||||
},
|
||||
"spec": {
|
||||
"panelId": 1,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "datasource",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "-- Dashboard --"
|
||||
},
|
||||
"spec": {
|
||||
"panelId": 2,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"refId": "B",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "datasource",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "-- Dashboard --"
|
||||
},
|
||||
"spec": {
|
||||
"panelId": 3,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"refId": "C",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "VizConfig",
|
||||
"group": "stat",
|
||||
"version": "12.1.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
@@ -973,6 +1167,32 @@
|
||||
"name": "panel-6"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 0,
|
||||
"y": 6,
|
||||
"width": 8,
|
||||
"height": 3,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-7"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 8,
|
||||
"y": 6,
|
||||
"width": 8,
|
||||
"height": 3,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-8"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+140
@@ -711,6 +711,146 @@
|
||||
],
|
||||
"title": "Mixed DS WITHOUT REFS",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 18
|
||||
},
|
||||
"id": 7,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 1,
|
||||
"refId": "A",
|
||||
"withTransforms": true
|
||||
}
|
||||
],
|
||||
"title": "Single Dashboard DS Query",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "mixed",
|
||||
"uid": "-- Mixed --"
|
||||
},
|
||||
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 18
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 1,
|
||||
"refId": "A",
|
||||
"withTransforms": true
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 2,
|
||||
"refId": "B",
|
||||
"withTransforms": true
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 3,
|
||||
"refId": "C",
|
||||
"withTransforms": true
|
||||
}
|
||||
],
|
||||
"title": "Multiple Dashboard DS Queries",
|
||||
"type": "stat"
|
||||
}
|
||||
],
|
||||
"preload": false,
|
||||
|
||||
Vendored
+140
@@ -711,6 +711,146 @@
|
||||
],
|
||||
"title": "Mixed DS WITHOUT REFS",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 18
|
||||
},
|
||||
"id": 7,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 1,
|
||||
"refId": "A",
|
||||
"withTransforms": true
|
||||
}
|
||||
],
|
||||
"title": "Single Dashboard DS Query",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "mixed",
|
||||
"uid": "-- Mixed --"
|
||||
},
|
||||
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 9,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 18
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 1,
|
||||
"refId": "A",
|
||||
"withTransforms": true
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 2,
|
||||
"refId": "B",
|
||||
"withTransforms": true
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 3,
|
||||
"refId": "C",
|
||||
"withTransforms": true
|
||||
}
|
||||
],
|
||||
"title": "Multiple Dashboard DS Queries",
|
||||
"type": "stat"
|
||||
}
|
||||
],
|
||||
"preload": false,
|
||||
|
||||
Vendored
+212
@@ -879,6 +879,200 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-7": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 7,
|
||||
"title": "Single Dashboard DS Query",
|
||||
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "datasource",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "-- Dashboard --"
|
||||
},
|
||||
"spec": {
|
||||
"panelId": 1,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "VizConfig",
|
||||
"group": "stat",
|
||||
"version": "12.1.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-8": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 8,
|
||||
"title": "Multiple Dashboard DS Queries",
|
||||
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "datasource",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "-- Dashboard --"
|
||||
},
|
||||
"spec": {
|
||||
"panelId": 1,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "datasource",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "-- Dashboard --"
|
||||
},
|
||||
"spec": {
|
||||
"panelId": 2,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"refId": "B",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "DataQuery",
|
||||
"group": "datasource",
|
||||
"version": "v0",
|
||||
"datasource": {
|
||||
"name": "-- Dashboard --"
|
||||
},
|
||||
"spec": {
|
||||
"panelId": 3,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"refId": "C",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "VizConfig",
|
||||
"group": "stat",
|
||||
"version": "12.1.0-pre",
|
||||
"spec": {
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
@@ -941,6 +1135,24 @@
|
||||
"name": "panel-6"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AutoGridLayoutItem",
|
||||
"spec": {
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-7"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "AutoGridLayoutItem",
|
||||
"spec": {
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-8"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+140
@@ -711,6 +711,146 @@
|
||||
],
|
||||
"title": "Mixed DS WITHOUT REFS",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 6
|
||||
},
|
||||
"id": 7,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 1,
|
||||
"refId": "A",
|
||||
"withTransforms": true
|
||||
}
|
||||
],
|
||||
"title": "Single Dashboard DS Query",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "mixed",
|
||||
"uid": "-- Mixed --"
|
||||
},
|
||||
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 6
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 1,
|
||||
"refId": "A",
|
||||
"withTransforms": true
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 2,
|
||||
"refId": "B",
|
||||
"withTransforms": true
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 3,
|
||||
"refId": "C",
|
||||
"withTransforms": true
|
||||
}
|
||||
],
|
||||
"title": "Multiple Dashboard DS Queries",
|
||||
"type": "stat"
|
||||
}
|
||||
],
|
||||
"preload": false,
|
||||
|
||||
+140
@@ -711,6 +711,146 @@
|
||||
],
|
||||
"title": "Mixed DS WITHOUT REFS",
|
||||
"type": "timeseries"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 8,
|
||||
"x": 0,
|
||||
"y": 6
|
||||
},
|
||||
"id": 7,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 1,
|
||||
"refId": "A",
|
||||
"withTransforms": true
|
||||
}
|
||||
],
|
||||
"title": "Single Dashboard DS Query",
|
||||
"type": "stat"
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "mixed",
|
||||
"uid": "-- Mixed --"
|
||||
},
|
||||
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
},
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"color": "green",
|
||||
"value": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"gridPos": {
|
||||
"h": 3,
|
||||
"w": 8,
|
||||
"x": 8,
|
||||
"y": 6
|
||||
},
|
||||
"id": 8,
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"targets": [
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 1,
|
||||
"refId": "A",
|
||||
"withTransforms": true
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 2,
|
||||
"refId": "B",
|
||||
"withTransforms": true
|
||||
},
|
||||
{
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"panelId": 3,
|
||||
"refId": "C",
|
||||
"withTransforms": true
|
||||
}
|
||||
],
|
||||
"title": "Multiple Dashboard DS Queries",
|
||||
"type": "stat"
|
||||
}
|
||||
],
|
||||
"preload": false,
|
||||
|
||||
Vendored
+214
@@ -852,6 +852,194 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-7": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 7,
|
||||
"title": "Single Dashboard DS Query",
|
||||
"description": "Panel with a single -- Dashboard -- datasource query",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "datasource",
|
||||
"spec": {
|
||||
"panelId": 1,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "stat",
|
||||
"spec": {
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"panel-8": {
|
||||
"kind": "Panel",
|
||||
"spec": {
|
||||
"id": 8,
|
||||
"title": "Multiple Dashboard DS Queries",
|
||||
"description": "Panel with multiple -- Dashboard -- datasource queries (should be mixed)",
|
||||
"links": [],
|
||||
"data": {
|
||||
"kind": "QueryGroup",
|
||||
"spec": {
|
||||
"queries": [
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "datasource",
|
||||
"spec": {
|
||||
"panelId": 1,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"refId": "A",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "datasource",
|
||||
"spec": {
|
||||
"panelId": 2,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"refId": "B",
|
||||
"hidden": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "PanelQuery",
|
||||
"spec": {
|
||||
"query": {
|
||||
"kind": "datasource",
|
||||
"spec": {
|
||||
"panelId": 3,
|
||||
"withTransforms": true
|
||||
}
|
||||
},
|
||||
"datasource": {
|
||||
"type": "datasource",
|
||||
"uid": "-- Dashboard --"
|
||||
},
|
||||
"refId": "C",
|
||||
"hidden": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"transformations": [],
|
||||
"queryOptions": {}
|
||||
}
|
||||
},
|
||||
"vizConfig": {
|
||||
"kind": "stat",
|
||||
"spec": {
|
||||
"pluginVersion": "12.1.0-pre",
|
||||
"options": {
|
||||
"colorMode": "value",
|
||||
"graphMode": "area",
|
||||
"justifyMode": "auto",
|
||||
"orientation": "auto",
|
||||
"percentChangeColorMode": "standard",
|
||||
"reduceOptions": {
|
||||
"calcs": [
|
||||
"lastNotNull"
|
||||
],
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showPercentChange": false,
|
||||
"textMode": "auto",
|
||||
"wideLayout": true
|
||||
},
|
||||
"fieldConfig": {
|
||||
"defaults": {
|
||||
"thresholds": {
|
||||
"mode": "absolute",
|
||||
"steps": [
|
||||
{
|
||||
"value": 0,
|
||||
"color": "green"
|
||||
}
|
||||
]
|
||||
},
|
||||
"color": {
|
||||
"mode": "thresholds"
|
||||
}
|
||||
},
|
||||
"overrides": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
@@ -946,6 +1134,32 @@
|
||||
"name": "panel-6"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 0,
|
||||
"y": 6,
|
||||
"width": 8,
|
||||
"height": 3,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-7"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "GridLayoutItem",
|
||||
"spec": {
|
||||
"x": 8,
|
||||
"y": 6,
|
||||
"width": 8,
|
||||
"height": 3,
|
||||
"element": {
|
||||
"kind": "ElementReference",
|
||||
"name": "panel-8"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1195,16 +1195,36 @@ func getDataSourceForQuery(explicitDS *dashv2alpha1.DashboardDataSourceRef, quer
|
||||
// getPanelDatasource determines the panel-level datasource for V1.
|
||||
// Returns:
|
||||
// - Mixed datasource reference if queries use different datasources
|
||||
// - Mixed datasource reference if multiple queries use Dashboard datasource (they fetch from different panels)
|
||||
// - Dashboard datasource reference if a single query uses Dashboard datasource
|
||||
// - First query's datasource if all queries use the same datasource
|
||||
// - nil if no queries exist
|
||||
// Compares based on V2 input without runtime resolution:
|
||||
// - If query has explicit datasource.uid → use that UID and type
|
||||
// - Else → use query.Kind as type (empty UID)
|
||||
func getPanelDatasource(queries []dashv2alpha1.DashboardPanelQueryKind) map[string]interface{} {
|
||||
const sharedDashboardQuery = "-- Dashboard --"
|
||||
|
||||
if len(queries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count how many queries use Dashboard datasource
|
||||
// Multiple dashboard queries need mixed mode because they fetch from different panels
|
||||
// which may have different underlying datasources
|
||||
dashboardDsQueryCount := 0
|
||||
for _, query := range queries {
|
||||
if query.Spec.Datasource != nil && query.Spec.Datasource.Uid != nil && *query.Spec.Datasource.Uid == sharedDashboardQuery {
|
||||
dashboardDsQueryCount++
|
||||
}
|
||||
}
|
||||
if dashboardDsQueryCount > 1 {
|
||||
return map[string]interface{}{
|
||||
"type": "mixed",
|
||||
"uid": "-- Mixed --",
|
||||
}
|
||||
}
|
||||
|
||||
var firstUID, firstType string
|
||||
var hasFirst bool
|
||||
|
||||
@@ -1239,6 +1259,16 @@ func getPanelDatasource(queries []dashv2alpha1.DashboardPanelQueryKind) map[stri
|
||||
}
|
||||
}
|
||||
|
||||
// Handle case when a single query uses Dashboard datasource.
|
||||
// This is needed for the frontend to properly activate and fetch data from source panels.
|
||||
// See DashboardDatasourceBehaviour.tsx for more details.
|
||||
if firstUID == sharedDashboardQuery {
|
||||
return map[string]interface{}{
|
||||
"type": "datasource",
|
||||
"uid": sharedDashboardQuery,
|
||||
}
|
||||
}
|
||||
|
||||
// Not mixed - return the first query's datasource so the panel has a datasource set.
|
||||
// This is required because the frontend's legacy PanelModel.PanelQueryRunner.run uses panel.datasource
|
||||
// to resolve the datasource, and if undefined, it falls back to the default datasource
|
||||
|
||||
+22
-4
@@ -290,7 +290,7 @@
|
||||
],
|
||||
"legend": {
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"placement": "right",
|
||||
"showLegend": true,
|
||||
"values": [
|
||||
"percent"
|
||||
@@ -304,7 +304,7 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showLegend": false,
|
||||
"showLegend": true,
|
||||
"strokeWidth": 1,
|
||||
"text": {}
|
||||
},
|
||||
@@ -323,6 +323,15 @@
|
||||
}
|
||||
],
|
||||
"title": "Percent",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "renameByRegex",
|
||||
"options": {
|
||||
"regex": "^Backend-(.*)$",
|
||||
"renamePattern": "b-$1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "piechart"
|
||||
},
|
||||
{
|
||||
@@ -366,7 +375,7 @@
|
||||
],
|
||||
"legend": {
|
||||
"displayMode": "table",
|
||||
"placement": "bottom",
|
||||
"placement": "right",
|
||||
"showLegend": true,
|
||||
"values": [
|
||||
"value"
|
||||
@@ -380,7 +389,7 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showLegend": false,
|
||||
"showLegend": true,
|
||||
"strokeWidth": 1,
|
||||
"text": {}
|
||||
},
|
||||
@@ -399,6 +408,15 @@
|
||||
}
|
||||
],
|
||||
"title": "Value",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "renameByRegex",
|
||||
"options": {
|
||||
"regex": "(.*)",
|
||||
"renamePattern": "$1-how-much-wood-could-a-woodchuck-chuck-if-a-woodchuck-could-chuck-wood"
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "piechart"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
)
|
||||
|
||||
func (u User) AuthID() string {
|
||||
meta, err := utils.MetaAccessor(&u)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// TODO: Workaround until we move all definitions
|
||||
// After having all resource definitions here in the app, we can remove this
|
||||
// and we need to change the List authorization to use the MetaAccessor and the GetDeprecatedInternalID method
|
||||
//nolint:staticcheck
|
||||
return fmt.Sprintf("%d", meta.GetDeprecatedInternalID())
|
||||
}
|
||||
|
||||
func (s ServiceAccount) AuthID() string {
|
||||
meta, err := utils.MetaAccessor(&s)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// TODO: Workaround until we move all definitions
|
||||
// After having all resource definitions here in the app, we can remove this
|
||||
// and we need to change the List authorization to use the MetaAccessor and the GetDeprecatedInternalID method
|
||||
//nolint:staticcheck
|
||||
return fmt.Sprintf("%d", meta.GetDeprecatedInternalID())
|
||||
}
|
||||
|
||||
func (t Team) AuthID() string {
|
||||
meta, err := utils.MetaAccessor(&t)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// TODO: Workaround until we move all definitions
|
||||
// After having all resource definitions here in the app, we can remove this
|
||||
// and we need to change the List authorization to use the MetaAccessor and the GetDeprecatedInternalID method
|
||||
//nolint:staticcheck
|
||||
return fmt.Sprintf("%d", meta.GetDeprecatedInternalID())
|
||||
}
|
||||
@@ -191,7 +191,13 @@
|
||||
}
|
||||
},
|
||||
"conversion": false
|
||||
},
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "v1beta1",
|
||||
"served": true,
|
||||
"kinds": [
|
||||
{
|
||||
"kind": "LogsDrilldownDefaultColumns",
|
||||
"plural": "LogsDrilldownDefaultColumns",
|
||||
@@ -314,6 +320,6 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
"preferredVersion": "v1alpha1"
|
||||
"preferredVersion": "v1beta1"
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
"group": "logsdrilldown.grafana.app",
|
||||
"versions": [
|
||||
{
|
||||
"name": "v1alpha1",
|
||||
"name": "v1beta1",
|
||||
"served": true,
|
||||
"storage": true,
|
||||
"schema": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package kinds
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/apps/logsdrilldown/kinds/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/logsdrilldown/kinds/v1beta1",
|
||||
)
|
||||
|
||||
LogsDrilldownSpecv1alpha1: {
|
||||
@@ -26,11 +26,11 @@ logsdrilldownDefaultsv1alpha1: {
|
||||
}
|
||||
}
|
||||
|
||||
// Default columns API
|
||||
logsdrilldownDefaultColumnsv0alpha1: {
|
||||
// Default columns API (beta)
|
||||
logsdrilldownDefaultColumnsv1beta1: {
|
||||
kind: "LogsDrilldownDefaultColumns"
|
||||
pluralName: "LogsDrilldownDefaultColumns"
|
||||
schema: {
|
||||
spec: v0alpha1.LogsDefaultColumns
|
||||
spec: v1beta1.LogsDefaultColumns
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ manifest: {
|
||||
// If your app needs access to kinds managed by another app, use permissions.accessKinds to allow your app access.
|
||||
versions: {
|
||||
"v1alpha1": v1alpha1
|
||||
"v1beta1" : v1beta1
|
||||
}
|
||||
// extraPermissions contains any additional permissions your app may require to function.
|
||||
// Your app will always have all permissions for each kind it manages (the items defined in 'kinds').
|
||||
@@ -35,7 +36,40 @@ manifest: {
|
||||
// It includes kinds which the v1alpha1 API serves, and (future) custom routes served globally from the v1alpha1 version.
|
||||
v1alpha1: {
|
||||
// kinds is the list of kinds served by this version
|
||||
kinds: [logsdrilldownv1alpha1, logsdrilldownDefaultsv1alpha1, logsdrilldownDefaultColumnsv0alpha1]
|
||||
kinds: [logsdrilldownv1alpha1, logsdrilldownDefaultsv1alpha1]
|
||||
// [OPTIONAL]
|
||||
// served indicates whether this particular version is served by the API server.
|
||||
// served should be set to false before a version is removed from the manifest entirely.
|
||||
// served defaults to true if not present.
|
||||
served: true
|
||||
// [OPTIONAL]
|
||||
// Codegen is a trait that tells the grafana-app-sdk, or other code generation tooling, how to process this kind.
|
||||
// If not present, default values within the codegen trait are used.
|
||||
// If you wish to specify codegen per-version, put this section in the version's object
|
||||
// (for example, <no value>v1alpha1) instead.
|
||||
codegen: {
|
||||
// [OPTIONAL]
|
||||
// ts contains TypeScript code generation properties for the kind
|
||||
ts: {
|
||||
// [OPTIONAL]
|
||||
// enabled indicates whether the CLI should generate front-end TypeScript code for the kind.
|
||||
// Defaults to true if not present.
|
||||
enabled: true
|
||||
}
|
||||
// [OPTIONAL]
|
||||
// go contains go code generation properties for the kind
|
||||
go: {
|
||||
// [OPTIONAL]
|
||||
// enabled indicates whether the CLI should generate back-end go code for the kind.
|
||||
// Defaults to true if not present.
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v1beta1: {
|
||||
// kinds is the list of kinds served by this version
|
||||
kinds: [logsdrilldownDefaultColumnsv1beta1]
|
||||
// [OPTIONAL]
|
||||
// served indicates whether this particular version is served by the API server.
|
||||
// served should be set to false before a version is removed from the manifest entirely.
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package v1beta1
|
||||
|
||||
#LogsDefaultColumnsLabel: {
|
||||
key: string
|
||||
value: string
|
||||
}
|
||||
|
||||
#LogsDefaultColumnsLabels: [...#LogsDefaultColumnsLabel]
|
||||
|
||||
#LogsDefaultColumnsRecord: {
|
||||
columns: [...string]
|
||||
labels: #LogsDefaultColumnsLabels
|
||||
}
|
||||
|
||||
#LogsDefaultColumnsRecords: [...#LogsDefaultColumnsRecord]
|
||||
|
||||
LogsDefaultColumns: {
|
||||
records: #LogsDefaultColumnsRecords
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package v1alpha1
|
||||
package v1beta1
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
@@ -6,7 +6,7 @@ const (
|
||||
// APIGroup is the API group used by all kinds in this package
|
||||
APIGroup = "logsdrilldown.grafana.app"
|
||||
// APIVersion is the API version used by all kinds in this package
|
||||
APIVersion = "v1alpha1"
|
||||
APIVersion = "v1beta1"
|
||||
)
|
||||
|
||||
var (
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package v1alpha1
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"context"
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
time "time"
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
// schema is unexported to prevent accidental overwrites
|
||||
var (
|
||||
schemaLogsDrilldownDefaultColumns = resource.NewSimpleSchema("logsdrilldown.grafana.app", "v1alpha1", NewLogsDrilldownDefaultColumns(), &LogsDrilldownDefaultColumnsList{}, resource.WithKind("LogsDrilldownDefaultColumns"),
|
||||
schemaLogsDrilldownDefaultColumns = resource.NewSimpleSchema("logsdrilldown.grafana.app", "v1beta1", NewLogsDrilldownDefaultColumns(), &LogsDrilldownDefaultColumnsList{}, resource.WithKind("LogsDrilldownDefaultColumns"),
|
||||
resource.WithPlural("logsdrilldowndefaultcolumns"), resource.WithScope(resource.NamespacedScope))
|
||||
kindLogsDrilldownDefaultColumns = resource.Kind{
|
||||
Schema: schemaLogsDrilldownDefaultColumns,
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
package v1beta1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDrilldownDefaultColumnsLogsDefaultColumnsRecords []LogsDrilldownDefaultColumnsLogsDefaultColumnsRecord
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
package v1beta1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDrilldownDefaultColumnsstatusOperatorState struct {
|
||||
+26
-14
@@ -17,24 +17,25 @@ import (
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
|
||||
v1alpha1 "github.com/grafana/grafana/apps/logsdrilldown/pkg/apis/logsdrilldown/v1alpha1"
|
||||
v1beta1 "github.com/grafana/grafana/apps/logsdrilldown/pkg/apis/logsdrilldown/v1beta1"
|
||||
)
|
||||
|
||||
var (
|
||||
rawSchemaLogsDrilldownv1alpha1 = []byte(`{"LogsDrilldown":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"defaultFields":{"items":{"type":"string"},"type":"array"},"interceptDismissed":{"type":"boolean"},"prettifyJSON":{"type":"boolean"},"wrapLogMessage":{"type":"boolean"}},"required":["defaultFields","prettifyJSON","wrapLogMessage","interceptDismissed"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||
versionSchemaLogsDrilldownv1alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaLogsDrilldownv1alpha1, &versionSchemaLogsDrilldownv1alpha1)
|
||||
rawSchemaLogsDrilldownDefaultsv1alpha1 = []byte(`{"LogsDrilldownDefaults":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"defaultFields":{"items":{"type":"string"},"type":"array"},"interceptDismissed":{"type":"boolean"},"prettifyJSON":{"type":"boolean"},"wrapLogMessage":{"type":"boolean"}},"required":["defaultFields","prettifyJSON","wrapLogMessage","interceptDismissed"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||
versionSchemaLogsDrilldownDefaultsv1alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaLogsDrilldownDefaultsv1alpha1, &versionSchemaLogsDrilldownDefaultsv1alpha1)
|
||||
rawSchemaLogsDrilldownDefaultColumnsv1alpha1 = []byte(`{"LogsDefaultColumnsLabel":{"additionalProperties":false,"properties":{"key":{"type":"string"},"value":{"type":"string"}},"required":["key","value"],"type":"object"},"LogsDefaultColumnsLabels":{"items":{"$ref":"#/components/schemas/LogsDefaultColumnsLabel"},"type":"array"},"LogsDefaultColumnsRecord":{"additionalProperties":false,"properties":{"columns":{"items":{"type":"string"},"type":"array"},"labels":{"$ref":"#/components/schemas/LogsDefaultColumnsLabels"}},"required":["columns","labels"],"type":"object"},"LogsDefaultColumnsRecords":{"items":{"$ref":"#/components/schemas/LogsDefaultColumnsRecord"},"type":"array"},"LogsDrilldownDefaultColumns":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"records":{"$ref":"#/components/schemas/LogsDefaultColumnsRecords"}},"required":["records"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||
versionSchemaLogsDrilldownDefaultColumnsv1alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaLogsDrilldownDefaultColumnsv1alpha1, &versionSchemaLogsDrilldownDefaultColumnsv1alpha1)
|
||||
rawSchemaLogsDrilldownv1alpha1 = []byte(`{"LogsDrilldown":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"defaultFields":{"items":{"type":"string"},"type":"array"},"interceptDismissed":{"type":"boolean"},"prettifyJSON":{"type":"boolean"},"wrapLogMessage":{"type":"boolean"}},"required":["defaultFields","prettifyJSON","wrapLogMessage","interceptDismissed"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||
versionSchemaLogsDrilldownv1alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaLogsDrilldownv1alpha1, &versionSchemaLogsDrilldownv1alpha1)
|
||||
rawSchemaLogsDrilldownDefaultsv1alpha1 = []byte(`{"LogsDrilldownDefaults":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"defaultFields":{"items":{"type":"string"},"type":"array"},"interceptDismissed":{"type":"boolean"},"prettifyJSON":{"type":"boolean"},"wrapLogMessage":{"type":"boolean"}},"required":["defaultFields","prettifyJSON","wrapLogMessage","interceptDismissed"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||
versionSchemaLogsDrilldownDefaultsv1alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaLogsDrilldownDefaultsv1alpha1, &versionSchemaLogsDrilldownDefaultsv1alpha1)
|
||||
rawSchemaLogsDrilldownDefaultColumnsv1beta1 = []byte(`{"LogsDefaultColumnsLabel":{"additionalProperties":false,"properties":{"key":{"type":"string"},"value":{"type":"string"}},"required":["key","value"],"type":"object"},"LogsDefaultColumnsLabels":{"items":{"$ref":"#/components/schemas/LogsDefaultColumnsLabel"},"type":"array"},"LogsDefaultColumnsRecord":{"additionalProperties":false,"properties":{"columns":{"items":{"type":"string"},"type":"array"},"labels":{"$ref":"#/components/schemas/LogsDefaultColumnsLabels"}},"required":["columns","labels"],"type":"object"},"LogsDefaultColumnsRecords":{"items":{"$ref":"#/components/schemas/LogsDefaultColumnsRecord"},"type":"array"},"LogsDrilldownDefaultColumns":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"records":{"$ref":"#/components/schemas/LogsDefaultColumnsRecords"}},"required":["records"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||
versionSchemaLogsDrilldownDefaultColumnsv1beta1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaLogsDrilldownDefaultColumnsv1beta1, &versionSchemaLogsDrilldownDefaultColumnsv1beta1)
|
||||
)
|
||||
|
||||
var appManifestData = app.ManifestData{
|
||||
AppName: "logsdrilldown",
|
||||
Group: "logsdrilldown.grafana.app",
|
||||
PreferredVersion: "v1alpha1",
|
||||
PreferredVersion: "v1beta1",
|
||||
Versions: []app.ManifestVersion{
|
||||
{
|
||||
Name: "v1alpha1",
|
||||
@@ -55,13 +56,24 @@ var appManifestData = app.ManifestData{
|
||||
Conversion: false,
|
||||
Schema: &versionSchemaLogsDrilldownDefaultsv1alpha1,
|
||||
},
|
||||
},
|
||||
Routes: app.ManifestVersionRoutes{
|
||||
Namespaced: map[string]spec3.PathProps{},
|
||||
Cluster: map[string]spec3.PathProps{},
|
||||
Schemas: map[string]spec.Schema{},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Name: "v1beta1",
|
||||
Served: true,
|
||||
Kinds: []app.ManifestVersionKind{
|
||||
{
|
||||
Kind: "LogsDrilldownDefaultColumns",
|
||||
Plural: "LogsDrilldownDefaultColumns",
|
||||
Scope: "Namespaced",
|
||||
Conversion: false,
|
||||
Schema: &versionSchemaLogsDrilldownDefaultColumnsv1alpha1,
|
||||
Schema: &versionSchemaLogsDrilldownDefaultColumnsv1beta1,
|
||||
},
|
||||
},
|
||||
Routes: app.ManifestVersionRoutes{
|
||||
@@ -82,9 +94,9 @@ func RemoteManifest() app.Manifest {
|
||||
}
|
||||
|
||||
var kindVersionToGoType = map[string]resource.Kind{
|
||||
"LogsDrilldown/v1alpha1": v1alpha1.LogsDrilldownKind(),
|
||||
"LogsDrilldownDefaults/v1alpha1": v1alpha1.LogsDrilldownDefaultsKind(),
|
||||
"LogsDrilldownDefaultColumns/v1alpha1": v1alpha1.LogsDrilldownDefaultColumnsKind(),
|
||||
"LogsDrilldown/v1alpha1": v1alpha1.LogsDrilldownKind(),
|
||||
"LogsDrilldownDefaults/v1alpha1": v1alpha1.LogsDrilldownDefaultsKind(),
|
||||
"LogsDrilldownDefaultColumns/v1beta1": v1beta1.LogsDrilldownDefaultColumnsKind(),
|
||||
}
|
||||
|
||||
// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists.
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
logsdrilldownv1alpha1 "github.com/grafana/grafana/apps/logsdrilldown/pkg/apis/logsdrilldown/v1alpha1"
|
||||
logsdrilldownv1beta1 "github.com/grafana/grafana/apps/logsdrilldown/pkg/apis/logsdrilldown/v1beta1"
|
||||
)
|
||||
|
||||
func New(cfg app.Config) (app.App, error) {
|
||||
@@ -32,7 +33,7 @@ func New(cfg app.Config) (app.App, error) {
|
||||
Kind: logsdrilldownv1alpha1.LogsDrilldownDefaultsKind(),
|
||||
},
|
||||
{
|
||||
Kind: logsdrilldownv1alpha1.LogsDrilldownDefaultColumnsKind(),
|
||||
Kind: logsdrilldownv1beta1.LogsDrilldownDefaultColumnsKind(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type LogsDrilldownClient struct {
|
||||
client *resource.TypedClient[*LogsDrilldown, *LogsDrilldownList]
|
||||
}
|
||||
|
||||
func NewLogsDrilldownClient(client resource.Client) *LogsDrilldownClient {
|
||||
return &LogsDrilldownClient{
|
||||
client: resource.NewTypedClient[*LogsDrilldown, *LogsDrilldownList](client, Kind()),
|
||||
}
|
||||
}
|
||||
|
||||
func NewLogsDrilldownClientFromGenerator(generator resource.ClientGenerator) (*LogsDrilldownClient, error) {
|
||||
c, err := generator.ClientFor(Kind())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewLogsDrilldownClient(c), nil
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownClient) Get(ctx context.Context, identifier resource.Identifier) (*LogsDrilldown, error) {
|
||||
return c.client.Get(ctx, identifier)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*LogsDrilldownList, error) {
|
||||
return c.client.List(ctx, namespace, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*LogsDrilldownList, error) {
|
||||
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Limit: opts.Limit,
|
||||
LabelFilters: opts.LabelFilters,
|
||||
FieldSelectors: opts.FieldSelectors,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for resp.GetContinue() != "" {
|
||||
page, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||
Continue: resp.GetContinue(),
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Limit: opts.Limit,
|
||||
LabelFilters: opts.LabelFilters,
|
||||
FieldSelectors: opts.FieldSelectors,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.SetContinue(page.GetContinue())
|
||||
resp.SetResourceVersion(page.GetResourceVersion())
|
||||
resp.SetItems(append(resp.GetItems(), page.GetItems()...))
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownClient) Create(ctx context.Context, obj *LogsDrilldown, opts resource.CreateOptions) (*LogsDrilldown, error) {
|
||||
// Make sure apiVersion and kind are set
|
||||
obj.APIVersion = GroupVersion.Identifier()
|
||||
obj.Kind = Kind().Kind()
|
||||
return c.client.Create(ctx, obj, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownClient) Update(ctx context.Context, obj *LogsDrilldown, opts resource.UpdateOptions) (*LogsDrilldown, error) {
|
||||
return c.client.Update(ctx, obj, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*LogsDrilldown, error) {
|
||||
return c.client.Patch(ctx, identifier, req, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus Status, opts resource.UpdateOptions) (*LogsDrilldown, error) {
|
||||
return c.client.Update(ctx, &LogsDrilldown{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: Kind().Kind(),
|
||||
APIVersion: GroupVersion.Identifier(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Namespace: identifier.Namespace,
|
||||
Name: identifier.Name,
|
||||
},
|
||||
Status: newStatus,
|
||||
}, resource.UpdateOptions{
|
||||
Subresource: "status",
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
||||
return c.client.Delete(ctx, identifier, opts)
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
)
|
||||
|
||||
// JSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
||||
type JSONCodec struct{}
|
||||
|
||||
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
||||
func (*JSONCodec) Read(reader io.Reader, into resource.Object) error {
|
||||
return json.NewDecoder(reader).Decode(into)
|
||||
}
|
||||
|
||||
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
|
||||
func (*JSONCodec) Write(writer io.Writer, from resource.Object) error {
|
||||
return json.NewEncoder(writer).Encode(from)
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Codec = &JSONCodec{}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
time "time"
|
||||
)
|
||||
|
||||
// metadata contains embedded CommonMetadata and can be extended with custom string fields
|
||||
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
|
||||
// without external reference as using the CommonMetadata reference breaks thema codegen.
|
||||
type Metadata struct {
|
||||
UpdateTimestamp time.Time `json:"updateTimestamp"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Uid string `json:"uid"`
|
||||
CreationTimestamp time.Time `json:"creationTimestamp"`
|
||||
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
|
||||
Finalizers []string `json:"finalizers"`
|
||||
ResourceVersion string `json:"resourceVersion"`
|
||||
Generation int64 `json:"generation"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
}
|
||||
|
||||
// NewMetadata creates a new Metadata object.
|
||||
func NewMetadata() *Metadata {
|
||||
return &Metadata{
|
||||
Finalizers: []string{},
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
}
|
||||
-319
@@ -1,319 +0,0 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDrilldown struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
||||
|
||||
// Spec is the spec of the LogsDrilldown
|
||||
Spec Spec `json:"spec" yaml:"spec"`
|
||||
|
||||
Status Status `json:"status" yaml:"status"`
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) GetSpec() any {
|
||||
return o.Spec
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) SetSpec(spec any) error {
|
||||
cast, ok := spec.(Spec)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
||||
}
|
||||
o.Spec = cast
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) GetSubresources() map[string]any {
|
||||
return map[string]any{
|
||||
"status": o.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) GetSubresource(name string) (any, bool) {
|
||||
switch name {
|
||||
case "status":
|
||||
return o.Status, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) SetSubresource(name string, value any) error {
|
||||
switch name {
|
||||
case "status":
|
||||
cast, ok := value.(Status)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set status type %#v, not of type Status", value)
|
||||
}
|
||||
o.Status = cast
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) GetStaticMetadata() resource.StaticMetadata {
|
||||
gvk := o.GroupVersionKind()
|
||||
return resource.StaticMetadata{
|
||||
Name: o.ObjectMeta.Name,
|
||||
Namespace: o.ObjectMeta.Namespace,
|
||||
Group: gvk.Group,
|
||||
Version: gvk.Version,
|
||||
Kind: gvk.Kind,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) SetStaticMetadata(metadata resource.StaticMetadata) {
|
||||
o.Name = metadata.Name
|
||||
o.Namespace = metadata.Namespace
|
||||
o.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: metadata.Group,
|
||||
Version: metadata.Version,
|
||||
Kind: metadata.Kind,
|
||||
})
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) GetCommonMetadata() resource.CommonMetadata {
|
||||
dt := o.DeletionTimestamp
|
||||
var deletionTimestamp *time.Time
|
||||
if dt != nil {
|
||||
deletionTimestamp = &dt.Time
|
||||
}
|
||||
// Legacy ExtraFields support
|
||||
extraFields := make(map[string]any)
|
||||
if o.Annotations != nil {
|
||||
extraFields["annotations"] = o.Annotations
|
||||
}
|
||||
if o.ManagedFields != nil {
|
||||
extraFields["managedFields"] = o.ManagedFields
|
||||
}
|
||||
if o.OwnerReferences != nil {
|
||||
extraFields["ownerReferences"] = o.OwnerReferences
|
||||
}
|
||||
return resource.CommonMetadata{
|
||||
UID: string(o.UID),
|
||||
ResourceVersion: o.ResourceVersion,
|
||||
Generation: o.Generation,
|
||||
Labels: o.Labels,
|
||||
CreationTimestamp: o.CreationTimestamp.Time,
|
||||
DeletionTimestamp: deletionTimestamp,
|
||||
Finalizers: o.Finalizers,
|
||||
UpdateTimestamp: o.GetUpdateTimestamp(),
|
||||
CreatedBy: o.GetCreatedBy(),
|
||||
UpdatedBy: o.GetUpdatedBy(),
|
||||
ExtraFields: extraFields,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) SetCommonMetadata(metadata resource.CommonMetadata) {
|
||||
o.UID = types.UID(metadata.UID)
|
||||
o.ResourceVersion = metadata.ResourceVersion
|
||||
o.Generation = metadata.Generation
|
||||
o.Labels = metadata.Labels
|
||||
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
|
||||
if metadata.DeletionTimestamp != nil {
|
||||
dt := metav1.NewTime(*metadata.DeletionTimestamp)
|
||||
o.DeletionTimestamp = &dt
|
||||
} else {
|
||||
o.DeletionTimestamp = nil
|
||||
}
|
||||
o.Finalizers = metadata.Finalizers
|
||||
if o.Annotations == nil {
|
||||
o.Annotations = make(map[string]string)
|
||||
}
|
||||
if !metadata.UpdateTimestamp.IsZero() {
|
||||
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
|
||||
}
|
||||
if metadata.CreatedBy != "" {
|
||||
o.SetCreatedBy(metadata.CreatedBy)
|
||||
}
|
||||
if metadata.UpdatedBy != "" {
|
||||
o.SetUpdatedBy(metadata.UpdatedBy)
|
||||
}
|
||||
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
|
||||
if metadata.ExtraFields != nil {
|
||||
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
|
||||
if cast, ok := annotations.(map[string]string); ok {
|
||||
o.Annotations = cast
|
||||
}
|
||||
}
|
||||
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
|
||||
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
|
||||
o.ManagedFields = cast
|
||||
}
|
||||
}
|
||||
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
|
||||
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
|
||||
o.OwnerReferences = cast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) GetCreatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) SetCreatedBy(createdBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) GetUpdateTimestamp() time.Time {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
|
||||
return parsed
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) SetUpdateTimestamp(updateTimestamp time.Time) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) GetUpdatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) SetUpdatedBy(updatedBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) Copy() resource.Object {
|
||||
return resource.CopyObject(o)
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) DeepCopy() *LogsDrilldown {
|
||||
cpy := &LogsDrilldown{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldown) DeepCopyInto(dst *LogsDrilldown) {
|
||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
||||
o.Spec.DeepCopyInto(&dst.Spec)
|
||||
o.Status.DeepCopyInto(&dst.Status)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.Object = &LogsDrilldown{}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDrilldownList struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
||||
Items []LogsDrilldown `json:"items" yaml:"items"`
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownList) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownList) Copy() resource.ListObject {
|
||||
cpy := &LogsDrilldownList{
|
||||
TypeMeta: o.TypeMeta,
|
||||
Items: make([]LogsDrilldown, len(o.Items)),
|
||||
}
|
||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
||||
for i := 0; i < len(o.Items); i++ {
|
||||
if item, ok := o.Items[i].Copy().(*LogsDrilldown); ok {
|
||||
cpy.Items[i] = *item
|
||||
}
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownList) GetItems() []resource.Object {
|
||||
items := make([]resource.Object, len(o.Items))
|
||||
for i := 0; i < len(o.Items); i++ {
|
||||
items[i] = &o.Items[i]
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownList) SetItems(items []resource.Object) {
|
||||
o.Items = make([]LogsDrilldown, len(items))
|
||||
for i := 0; i < len(items); i++ {
|
||||
o.Items[i] = *items[i].(*LogsDrilldown)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownList) DeepCopy() *LogsDrilldownList {
|
||||
cpy := &LogsDrilldownList{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownList) DeepCopyInto(dst *LogsDrilldownList) {
|
||||
resource.CopyObjectInto(dst, o)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.ListObject = &LogsDrilldownList{}
|
||||
|
||||
// Copy methods for all subresource types
|
||||
|
||||
// DeepCopy creates a full deep copy of Spec
|
||||
func (s *Spec) DeepCopy() *Spec {
|
||||
cpy := &Spec{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies Spec into another Spec object
|
||||
func (s *Spec) DeepCopyInto(dst *Spec) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
|
||||
// DeepCopy creates a full deep copy of Status
|
||||
func (s *Status) DeepCopy() *Status {
|
||||
cpy := &Status{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies Status into another Status object
|
||||
func (s *Status) DeepCopyInto(dst *Status) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
)
|
||||
|
||||
// schema is unexported to prevent accidental overwrites
|
||||
var (
|
||||
schemaLogsDrilldown = resource.NewSimpleSchema("logsdrilldown.grafana.app", "v1alpha1", &LogsDrilldown{}, &LogsDrilldownList{}, resource.WithKind("LogsDrilldown"),
|
||||
resource.WithPlural("logsdrilldowns"), resource.WithScope(resource.NamespacedScope))
|
||||
kindLogsDrilldown = resource.Kind{
|
||||
Schema: schemaLogsDrilldown,
|
||||
Codecs: map[resource.KindEncoding]resource.Codec{
|
||||
resource.KindEncodingJSON: &JSONCodec{},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Kind returns a resource.Kind for this Schema with a JSON codec
|
||||
func Kind() resource.Kind {
|
||||
return kindLogsDrilldown
|
||||
}
|
||||
|
||||
// Schema returns a resource.SimpleSchema representation of LogsDrilldown
|
||||
func Schema() *resource.SimpleSchema {
|
||||
return schemaLogsDrilldown
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Schema = kindLogsDrilldown
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type Spec struct {
|
||||
DefaultFields []string `json:"defaultFields"`
|
||||
PrettifyJSON bool `json:"prettifyJSON"`
|
||||
WrapLogMessage bool `json:"wrapLogMessage"`
|
||||
InterceptDismissed bool `json:"interceptDismissed"`
|
||||
}
|
||||
|
||||
// NewSpec creates a new Spec object.
|
||||
func NewSpec() *Spec {
|
||||
return &Spec{
|
||||
DefaultFields: []string{},
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StatusOperatorState struct {
|
||||
// lastEvaluation is the ResourceVersion last evaluated
|
||||
LastEvaluation string `json:"lastEvaluation"`
|
||||
// state describes the state of the lastEvaluation.
|
||||
// It is limited to three possible states for machine evaluation.
|
||||
State StatusOperatorStateState `json:"state"`
|
||||
// descriptiveState is an optional more descriptive state field which has no requirements on format
|
||||
DescriptiveState *string `json:"descriptiveState,omitempty"`
|
||||
// details contains any extra information that is operator-specific
|
||||
Details map[string]interface{} `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// NewStatusOperatorState creates a new StatusOperatorState object.
|
||||
func NewStatusOperatorState() *StatusOperatorState {
|
||||
return &StatusOperatorState{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type Status struct {
|
||||
// operatorStates is a map of operator ID to operator state evaluations.
|
||||
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
||||
OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"`
|
||||
// additionalFields is reserved for future use
|
||||
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
|
||||
}
|
||||
|
||||
// NewStatus creates a new Status object.
|
||||
func NewStatus() *Status {
|
||||
return &Status{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StatusOperatorStateState string
|
||||
|
||||
const (
|
||||
StatusOperatorStateStateSuccess StatusOperatorStateState = "success"
|
||||
StatusOperatorStateStateInProgress StatusOperatorStateState = "in_progress"
|
||||
StatusOperatorStateStateFailed StatusOperatorStateState = "failed"
|
||||
)
|
||||
@@ -1,18 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
const (
|
||||
// APIGroup is the API group used by all kinds in this package
|
||||
APIGroup = "logsdrilldown.grafana.app"
|
||||
// APIVersion is the API version used by all kinds in this package
|
||||
APIVersion = "v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
|
||||
GroupVersion = schema.GroupVersion{
|
||||
Group: APIGroup,
|
||||
Version: APIVersion,
|
||||
}
|
||||
)
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type LogsDrilldownDefaultColumnsClient struct {
|
||||
client *resource.TypedClient[*LogsDrilldownDefaultColumns, *LogsDrilldownDefaultColumnsList]
|
||||
}
|
||||
|
||||
func NewLogsDrilldownDefaultColumnsClient(client resource.Client) *LogsDrilldownDefaultColumnsClient {
|
||||
return &LogsDrilldownDefaultColumnsClient{
|
||||
client: resource.NewTypedClient[*LogsDrilldownDefaultColumns, *LogsDrilldownDefaultColumnsList](client, Kind()),
|
||||
}
|
||||
}
|
||||
|
||||
func NewLogsDrilldownDefaultColumnsClientFromGenerator(generator resource.ClientGenerator) (*LogsDrilldownDefaultColumnsClient, error) {
|
||||
c, err := generator.ClientFor(Kind())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewLogsDrilldownDefaultColumnsClient(c), nil
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultColumnsClient) Get(ctx context.Context, identifier resource.Identifier) (*LogsDrilldownDefaultColumns, error) {
|
||||
return c.client.Get(ctx, identifier)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultColumnsClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*LogsDrilldownDefaultColumnsList, error) {
|
||||
return c.client.List(ctx, namespace, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultColumnsClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*LogsDrilldownDefaultColumnsList, error) {
|
||||
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Limit: opts.Limit,
|
||||
LabelFilters: opts.LabelFilters,
|
||||
FieldSelectors: opts.FieldSelectors,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for resp.GetContinue() != "" {
|
||||
page, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||
Continue: resp.GetContinue(),
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Limit: opts.Limit,
|
||||
LabelFilters: opts.LabelFilters,
|
||||
FieldSelectors: opts.FieldSelectors,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.SetContinue(page.GetContinue())
|
||||
resp.SetResourceVersion(page.GetResourceVersion())
|
||||
resp.SetItems(append(resp.GetItems(), page.GetItems()...))
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultColumnsClient) Create(ctx context.Context, obj *LogsDrilldownDefaultColumns, opts resource.CreateOptions) (*LogsDrilldownDefaultColumns, error) {
|
||||
// Make sure apiVersion and kind are set
|
||||
obj.APIVersion = GroupVersion.Identifier()
|
||||
obj.Kind = Kind().Kind()
|
||||
return c.client.Create(ctx, obj, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultColumnsClient) Update(ctx context.Context, obj *LogsDrilldownDefaultColumns, opts resource.UpdateOptions) (*LogsDrilldownDefaultColumns, error) {
|
||||
return c.client.Update(ctx, obj, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultColumnsClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*LogsDrilldownDefaultColumns, error) {
|
||||
return c.client.Patch(ctx, identifier, req, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultColumnsClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus Status, opts resource.UpdateOptions) (*LogsDrilldownDefaultColumns, error) {
|
||||
return c.client.Update(ctx, &LogsDrilldownDefaultColumns{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: Kind().Kind(),
|
||||
APIVersion: GroupVersion.Identifier(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Namespace: identifier.Namespace,
|
||||
Name: identifier.Name,
|
||||
},
|
||||
Status: newStatus,
|
||||
}, resource.UpdateOptions{
|
||||
Subresource: "status",
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultColumnsClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
||||
return c.client.Delete(ctx, identifier, opts)
|
||||
}
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
)
|
||||
|
||||
// JSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
||||
type JSONCodec struct{}
|
||||
|
||||
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
||||
func (*JSONCodec) Read(reader io.Reader, into resource.Object) error {
|
||||
return json.NewDecoder(reader).Decode(into)
|
||||
}
|
||||
|
||||
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
|
||||
func (*JSONCodec) Write(writer io.Writer, from resource.Object) error {
|
||||
return json.NewEncoder(writer).Encode(from)
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Codec = &JSONCodec{}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
time "time"
|
||||
)
|
||||
|
||||
// metadata contains embedded CommonMetadata and can be extended with custom string fields
|
||||
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
|
||||
// without external reference as using the CommonMetadata reference breaks thema codegen.
|
||||
type Metadata struct {
|
||||
UpdateTimestamp time.Time `json:"updateTimestamp"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Uid string `json:"uid"`
|
||||
CreationTimestamp time.Time `json:"creationTimestamp"`
|
||||
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
|
||||
Finalizers []string `json:"finalizers"`
|
||||
ResourceVersion string `json:"resourceVersion"`
|
||||
Generation int64 `json:"generation"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
}
|
||||
|
||||
// NewMetadata creates a new Metadata object.
|
||||
func NewMetadata() *Metadata {
|
||||
return &Metadata{
|
||||
Finalizers: []string{},
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
}
|
||||
-319
@@ -1,319 +0,0 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDrilldownDefaultColumns struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
||||
|
||||
// Spec is the spec of the LogsDrilldownDefaultColumns
|
||||
Spec Spec `json:"spec" yaml:"spec"`
|
||||
|
||||
Status Status `json:"status" yaml:"status"`
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) GetSpec() any {
|
||||
return o.Spec
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) SetSpec(spec any) error {
|
||||
cast, ok := spec.(Spec)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
||||
}
|
||||
o.Spec = cast
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) GetSubresources() map[string]any {
|
||||
return map[string]any{
|
||||
"status": o.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) GetSubresource(name string) (any, bool) {
|
||||
switch name {
|
||||
case "status":
|
||||
return o.Status, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) SetSubresource(name string, value any) error {
|
||||
switch name {
|
||||
case "status":
|
||||
cast, ok := value.(Status)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set status type %#v, not of type Status", value)
|
||||
}
|
||||
o.Status = cast
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) GetStaticMetadata() resource.StaticMetadata {
|
||||
gvk := o.GroupVersionKind()
|
||||
return resource.StaticMetadata{
|
||||
Name: o.ObjectMeta.Name,
|
||||
Namespace: o.ObjectMeta.Namespace,
|
||||
Group: gvk.Group,
|
||||
Version: gvk.Version,
|
||||
Kind: gvk.Kind,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) SetStaticMetadata(metadata resource.StaticMetadata) {
|
||||
o.Name = metadata.Name
|
||||
o.Namespace = metadata.Namespace
|
||||
o.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: metadata.Group,
|
||||
Version: metadata.Version,
|
||||
Kind: metadata.Kind,
|
||||
})
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) GetCommonMetadata() resource.CommonMetadata {
|
||||
dt := o.DeletionTimestamp
|
||||
var deletionTimestamp *time.Time
|
||||
if dt != nil {
|
||||
deletionTimestamp = &dt.Time
|
||||
}
|
||||
// Legacy ExtraFields support
|
||||
extraFields := make(map[string]any)
|
||||
if o.Annotations != nil {
|
||||
extraFields["annotations"] = o.Annotations
|
||||
}
|
||||
if o.ManagedFields != nil {
|
||||
extraFields["managedFields"] = o.ManagedFields
|
||||
}
|
||||
if o.OwnerReferences != nil {
|
||||
extraFields["ownerReferences"] = o.OwnerReferences
|
||||
}
|
||||
return resource.CommonMetadata{
|
||||
UID: string(o.UID),
|
||||
ResourceVersion: o.ResourceVersion,
|
||||
Generation: o.Generation,
|
||||
Labels: o.Labels,
|
||||
CreationTimestamp: o.CreationTimestamp.Time,
|
||||
DeletionTimestamp: deletionTimestamp,
|
||||
Finalizers: o.Finalizers,
|
||||
UpdateTimestamp: o.GetUpdateTimestamp(),
|
||||
CreatedBy: o.GetCreatedBy(),
|
||||
UpdatedBy: o.GetUpdatedBy(),
|
||||
ExtraFields: extraFields,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) SetCommonMetadata(metadata resource.CommonMetadata) {
|
||||
o.UID = types.UID(metadata.UID)
|
||||
o.ResourceVersion = metadata.ResourceVersion
|
||||
o.Generation = metadata.Generation
|
||||
o.Labels = metadata.Labels
|
||||
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
|
||||
if metadata.DeletionTimestamp != nil {
|
||||
dt := metav1.NewTime(*metadata.DeletionTimestamp)
|
||||
o.DeletionTimestamp = &dt
|
||||
} else {
|
||||
o.DeletionTimestamp = nil
|
||||
}
|
||||
o.Finalizers = metadata.Finalizers
|
||||
if o.Annotations == nil {
|
||||
o.Annotations = make(map[string]string)
|
||||
}
|
||||
if !metadata.UpdateTimestamp.IsZero() {
|
||||
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
|
||||
}
|
||||
if metadata.CreatedBy != "" {
|
||||
o.SetCreatedBy(metadata.CreatedBy)
|
||||
}
|
||||
if metadata.UpdatedBy != "" {
|
||||
o.SetUpdatedBy(metadata.UpdatedBy)
|
||||
}
|
||||
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
|
||||
if metadata.ExtraFields != nil {
|
||||
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
|
||||
if cast, ok := annotations.(map[string]string); ok {
|
||||
o.Annotations = cast
|
||||
}
|
||||
}
|
||||
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
|
||||
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
|
||||
o.ManagedFields = cast
|
||||
}
|
||||
}
|
||||
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
|
||||
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
|
||||
o.OwnerReferences = cast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) GetCreatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) SetCreatedBy(createdBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) GetUpdateTimestamp() time.Time {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
|
||||
return parsed
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) SetUpdateTimestamp(updateTimestamp time.Time) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) GetUpdatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) SetUpdatedBy(updatedBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) Copy() resource.Object {
|
||||
return resource.CopyObject(o)
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) DeepCopy() *LogsDrilldownDefaultColumns {
|
||||
cpy := &LogsDrilldownDefaultColumns{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumns) DeepCopyInto(dst *LogsDrilldownDefaultColumns) {
|
||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
||||
o.Spec.DeepCopyInto(&dst.Spec)
|
||||
o.Status.DeepCopyInto(&dst.Status)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.Object = &LogsDrilldownDefaultColumns{}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDrilldownDefaultColumnsList struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
||||
Items []LogsDrilldownDefaultColumns `json:"items" yaml:"items"`
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumnsList) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumnsList) Copy() resource.ListObject {
|
||||
cpy := &LogsDrilldownDefaultColumnsList{
|
||||
TypeMeta: o.TypeMeta,
|
||||
Items: make([]LogsDrilldownDefaultColumns, len(o.Items)),
|
||||
}
|
||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
||||
for i := 0; i < len(o.Items); i++ {
|
||||
if item, ok := o.Items[i].Copy().(*LogsDrilldownDefaultColumns); ok {
|
||||
cpy.Items[i] = *item
|
||||
}
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumnsList) GetItems() []resource.Object {
|
||||
items := make([]resource.Object, len(o.Items))
|
||||
for i := 0; i < len(o.Items); i++ {
|
||||
items[i] = &o.Items[i]
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumnsList) SetItems(items []resource.Object) {
|
||||
o.Items = make([]LogsDrilldownDefaultColumns, len(items))
|
||||
for i := 0; i < len(items); i++ {
|
||||
o.Items[i] = *items[i].(*LogsDrilldownDefaultColumns)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumnsList) DeepCopy() *LogsDrilldownDefaultColumnsList {
|
||||
cpy := &LogsDrilldownDefaultColumnsList{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultColumnsList) DeepCopyInto(dst *LogsDrilldownDefaultColumnsList) {
|
||||
resource.CopyObjectInto(dst, o)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.ListObject = &LogsDrilldownDefaultColumnsList{}
|
||||
|
||||
// Copy methods for all subresource types
|
||||
|
||||
// DeepCopy creates a full deep copy of Spec
|
||||
func (s *Spec) DeepCopy() *Spec {
|
||||
cpy := &Spec{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies Spec into another Spec object
|
||||
func (s *Spec) DeepCopyInto(dst *Spec) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
|
||||
// DeepCopy creates a full deep copy of Status
|
||||
func (s *Status) DeepCopy() *Status {
|
||||
cpy := &Status{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies Status into another Status object
|
||||
func (s *Status) DeepCopyInto(dst *Status) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
)
|
||||
|
||||
// schema is unexported to prevent accidental overwrites
|
||||
var (
|
||||
schemaLogsDrilldownDefaultColumns = resource.NewSimpleSchema("logsdrilldown.grafana.app", "v1alpha1", &LogsDrilldownDefaultColumns{}, &LogsDrilldownDefaultColumnsList{}, resource.WithKind("LogsDrilldownDefaultColumns"),
|
||||
resource.WithPlural("logsdrilldowndefaultcolumns"), resource.WithScope(resource.NamespacedScope))
|
||||
kindLogsDrilldownDefaultColumns = resource.Kind{
|
||||
Schema: schemaLogsDrilldownDefaultColumns,
|
||||
Codecs: map[resource.KindEncoding]resource.Codec{
|
||||
resource.KindEncodingJSON: &JSONCodec{},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Kind returns a resource.Kind for this Schema with a JSON codec
|
||||
func Kind() resource.Kind {
|
||||
return kindLogsDrilldownDefaultColumns
|
||||
}
|
||||
|
||||
// Schema returns a resource.SimpleSchema representation of LogsDrilldownDefaultColumns
|
||||
func Schema() *resource.SimpleSchema {
|
||||
return schemaLogsDrilldownDefaultColumns
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Schema = kindLogsDrilldownDefaultColumns
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDefaultColumnsRecords []LogsDefaultColumnsRecord
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDefaultColumnsRecord struct {
|
||||
Columns []string `json:"columns"`
|
||||
Labels LogsDefaultColumnsLabels `json:"labels"`
|
||||
}
|
||||
|
||||
// NewLogsDefaultColumnsRecord creates a new LogsDefaultColumnsRecord object.
|
||||
func NewLogsDefaultColumnsRecord() *LogsDefaultColumnsRecord {
|
||||
return &LogsDefaultColumnsRecord{
|
||||
Columns: []string{},
|
||||
}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDefaultColumnsLabels []LogsDefaultColumnsLabel
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDefaultColumnsLabel struct {
|
||||
Key string `json:"key"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// NewLogsDefaultColumnsLabel creates a new LogsDefaultColumnsLabel object.
|
||||
func NewLogsDefaultColumnsLabel() *LogsDefaultColumnsLabel {
|
||||
return &LogsDefaultColumnsLabel{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type Spec struct {
|
||||
Records LogsDefaultColumnsRecords `json:"records"`
|
||||
}
|
||||
|
||||
// NewSpec creates a new Spec object.
|
||||
func NewSpec() *Spec {
|
||||
return &Spec{}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StatusOperatorState struct {
|
||||
// lastEvaluation is the ResourceVersion last evaluated
|
||||
LastEvaluation string `json:"lastEvaluation"`
|
||||
// state describes the state of the lastEvaluation.
|
||||
// It is limited to three possible states for machine evaluation.
|
||||
State StatusOperatorStateState `json:"state"`
|
||||
// descriptiveState is an optional more descriptive state field which has no requirements on format
|
||||
DescriptiveState *string `json:"descriptiveState,omitempty"`
|
||||
// details contains any extra information that is operator-specific
|
||||
Details map[string]interface{} `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// NewStatusOperatorState creates a new StatusOperatorState object.
|
||||
func NewStatusOperatorState() *StatusOperatorState {
|
||||
return &StatusOperatorState{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type Status struct {
|
||||
// operatorStates is a map of operator ID to operator state evaluations.
|
||||
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
||||
OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"`
|
||||
// additionalFields is reserved for future use
|
||||
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
|
||||
}
|
||||
|
||||
// NewStatus creates a new Status object.
|
||||
func NewStatus() *Status {
|
||||
return &Status{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StatusOperatorStateState string
|
||||
|
||||
const (
|
||||
StatusOperatorStateStateSuccess StatusOperatorStateState = "success"
|
||||
StatusOperatorStateStateInProgress StatusOperatorStateState = "in_progress"
|
||||
StatusOperatorStateStateFailed StatusOperatorStateState = "failed"
|
||||
)
|
||||
@@ -1,18 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
const (
|
||||
// APIGroup is the API group used by all kinds in this package
|
||||
APIGroup = "logsdrilldown.grafana.app"
|
||||
// APIVersion is the API version used by all kinds in this package
|
||||
APIVersion = "v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
// GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
|
||||
GroupVersion = schema.GroupVersion{
|
||||
Group: APIGroup,
|
||||
Version: APIVersion,
|
||||
}
|
||||
)
|
||||
Generated
-99
@@ -1,99 +0,0 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type LogsDrilldownDefaultsClient struct {
|
||||
client *resource.TypedClient[*LogsDrilldownDefaults, *LogsDrilldownDefaultsList]
|
||||
}
|
||||
|
||||
func NewLogsDrilldownDefaultsClient(client resource.Client) *LogsDrilldownDefaultsClient {
|
||||
return &LogsDrilldownDefaultsClient{
|
||||
client: resource.NewTypedClient[*LogsDrilldownDefaults, *LogsDrilldownDefaultsList](client, Kind()),
|
||||
}
|
||||
}
|
||||
|
||||
func NewLogsDrilldownDefaultsClientFromGenerator(generator resource.ClientGenerator) (*LogsDrilldownDefaultsClient, error) {
|
||||
c, err := generator.ClientFor(Kind())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewLogsDrilldownDefaultsClient(c), nil
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultsClient) Get(ctx context.Context, identifier resource.Identifier) (*LogsDrilldownDefaults, error) {
|
||||
return c.client.Get(ctx, identifier)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultsClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*LogsDrilldownDefaultsList, error) {
|
||||
return c.client.List(ctx, namespace, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultsClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*LogsDrilldownDefaultsList, error) {
|
||||
resp, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Limit: opts.Limit,
|
||||
LabelFilters: opts.LabelFilters,
|
||||
FieldSelectors: opts.FieldSelectors,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for resp.GetContinue() != "" {
|
||||
page, err := c.client.List(ctx, namespace, resource.ListOptions{
|
||||
Continue: resp.GetContinue(),
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Limit: opts.Limit,
|
||||
LabelFilters: opts.LabelFilters,
|
||||
FieldSelectors: opts.FieldSelectors,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.SetContinue(page.GetContinue())
|
||||
resp.SetResourceVersion(page.GetResourceVersion())
|
||||
resp.SetItems(append(resp.GetItems(), page.GetItems()...))
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultsClient) Create(ctx context.Context, obj *LogsDrilldownDefaults, opts resource.CreateOptions) (*LogsDrilldownDefaults, error) {
|
||||
// Make sure apiVersion and kind are set
|
||||
obj.APIVersion = GroupVersion.Identifier()
|
||||
obj.Kind = Kind().Kind()
|
||||
return c.client.Create(ctx, obj, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultsClient) Update(ctx context.Context, obj *LogsDrilldownDefaults, opts resource.UpdateOptions) (*LogsDrilldownDefaults, error) {
|
||||
return c.client.Update(ctx, obj, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultsClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*LogsDrilldownDefaults, error) {
|
||||
return c.client.Patch(ctx, identifier, req, opts)
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultsClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus Status, opts resource.UpdateOptions) (*LogsDrilldownDefaults, error) {
|
||||
return c.client.Update(ctx, &LogsDrilldownDefaults{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: Kind().Kind(),
|
||||
APIVersion: GroupVersion.Identifier(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
Namespace: identifier.Namespace,
|
||||
Name: identifier.Name,
|
||||
},
|
||||
Status: newStatus,
|
||||
}, resource.UpdateOptions{
|
||||
Subresource: "status",
|
||||
ResourceVersion: opts.ResourceVersion,
|
||||
})
|
||||
}
|
||||
|
||||
func (c *LogsDrilldownDefaultsClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
|
||||
return c.client.Delete(ctx, identifier, opts)
|
||||
}
|
||||
Generated
-28
@@ -1,28 +0,0 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
)
|
||||
|
||||
// JSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
|
||||
type JSONCodec struct{}
|
||||
|
||||
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
|
||||
func (*JSONCodec) Read(reader io.Reader, into resource.Object) error {
|
||||
return json.NewDecoder(reader).Decode(into)
|
||||
}
|
||||
|
||||
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
|
||||
func (*JSONCodec) Write(writer io.Writer, from resource.Object) error {
|
||||
return json.NewEncoder(writer).Encode(from)
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Codec = &JSONCodec{}
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
time "time"
|
||||
)
|
||||
|
||||
// metadata contains embedded CommonMetadata and can be extended with custom string fields
|
||||
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
|
||||
// without external reference as using the CommonMetadata reference breaks thema codegen.
|
||||
type Metadata struct {
|
||||
UpdateTimestamp time.Time `json:"updateTimestamp"`
|
||||
CreatedBy string `json:"createdBy"`
|
||||
Uid string `json:"uid"`
|
||||
CreationTimestamp time.Time `json:"creationTimestamp"`
|
||||
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
|
||||
Finalizers []string `json:"finalizers"`
|
||||
ResourceVersion string `json:"resourceVersion"`
|
||||
Generation int64 `json:"generation"`
|
||||
UpdatedBy string `json:"updatedBy"`
|
||||
Labels map[string]string `json:"labels"`
|
||||
}
|
||||
|
||||
// NewMetadata creates a new Metadata object.
|
||||
func NewMetadata() *Metadata {
|
||||
return &Metadata{
|
||||
Finalizers: []string{},
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
}
|
||||
Generated
-319
@@ -1,319 +0,0 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"time"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDrilldownDefaults struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
|
||||
|
||||
// Spec is the spec of the LogsDrilldownDefaults
|
||||
Spec Spec `json:"spec" yaml:"spec"`
|
||||
|
||||
Status Status `json:"status" yaml:"status"`
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) GetSpec() any {
|
||||
return o.Spec
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) SetSpec(spec any) error {
|
||||
cast, ok := spec.(Spec)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
||||
}
|
||||
o.Spec = cast
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) GetSubresources() map[string]any {
|
||||
return map[string]any{
|
||||
"status": o.Status,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) GetSubresource(name string) (any, bool) {
|
||||
switch name {
|
||||
case "status":
|
||||
return o.Status, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) SetSubresource(name string, value any) error {
|
||||
switch name {
|
||||
case "status":
|
||||
cast, ok := value.(Status)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set status type %#v, not of type Status", value)
|
||||
}
|
||||
o.Status = cast
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("subresource '%s' does not exist", name)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) GetStaticMetadata() resource.StaticMetadata {
|
||||
gvk := o.GroupVersionKind()
|
||||
return resource.StaticMetadata{
|
||||
Name: o.ObjectMeta.Name,
|
||||
Namespace: o.ObjectMeta.Namespace,
|
||||
Group: gvk.Group,
|
||||
Version: gvk.Version,
|
||||
Kind: gvk.Kind,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) SetStaticMetadata(metadata resource.StaticMetadata) {
|
||||
o.Name = metadata.Name
|
||||
o.Namespace = metadata.Namespace
|
||||
o.SetGroupVersionKind(schema.GroupVersionKind{
|
||||
Group: metadata.Group,
|
||||
Version: metadata.Version,
|
||||
Kind: metadata.Kind,
|
||||
})
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) GetCommonMetadata() resource.CommonMetadata {
|
||||
dt := o.DeletionTimestamp
|
||||
var deletionTimestamp *time.Time
|
||||
if dt != nil {
|
||||
deletionTimestamp = &dt.Time
|
||||
}
|
||||
// Legacy ExtraFields support
|
||||
extraFields := make(map[string]any)
|
||||
if o.Annotations != nil {
|
||||
extraFields["annotations"] = o.Annotations
|
||||
}
|
||||
if o.ManagedFields != nil {
|
||||
extraFields["managedFields"] = o.ManagedFields
|
||||
}
|
||||
if o.OwnerReferences != nil {
|
||||
extraFields["ownerReferences"] = o.OwnerReferences
|
||||
}
|
||||
return resource.CommonMetadata{
|
||||
UID: string(o.UID),
|
||||
ResourceVersion: o.ResourceVersion,
|
||||
Generation: o.Generation,
|
||||
Labels: o.Labels,
|
||||
CreationTimestamp: o.CreationTimestamp.Time,
|
||||
DeletionTimestamp: deletionTimestamp,
|
||||
Finalizers: o.Finalizers,
|
||||
UpdateTimestamp: o.GetUpdateTimestamp(),
|
||||
CreatedBy: o.GetCreatedBy(),
|
||||
UpdatedBy: o.GetUpdatedBy(),
|
||||
ExtraFields: extraFields,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) SetCommonMetadata(metadata resource.CommonMetadata) {
|
||||
o.UID = types.UID(metadata.UID)
|
||||
o.ResourceVersion = metadata.ResourceVersion
|
||||
o.Generation = metadata.Generation
|
||||
o.Labels = metadata.Labels
|
||||
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
|
||||
if metadata.DeletionTimestamp != nil {
|
||||
dt := metav1.NewTime(*metadata.DeletionTimestamp)
|
||||
o.DeletionTimestamp = &dt
|
||||
} else {
|
||||
o.DeletionTimestamp = nil
|
||||
}
|
||||
o.Finalizers = metadata.Finalizers
|
||||
if o.Annotations == nil {
|
||||
o.Annotations = make(map[string]string)
|
||||
}
|
||||
if !metadata.UpdateTimestamp.IsZero() {
|
||||
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
|
||||
}
|
||||
if metadata.CreatedBy != "" {
|
||||
o.SetCreatedBy(metadata.CreatedBy)
|
||||
}
|
||||
if metadata.UpdatedBy != "" {
|
||||
o.SetUpdatedBy(metadata.UpdatedBy)
|
||||
}
|
||||
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
|
||||
if metadata.ExtraFields != nil {
|
||||
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
|
||||
if cast, ok := annotations.(map[string]string); ok {
|
||||
o.Annotations = cast
|
||||
}
|
||||
}
|
||||
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
|
||||
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
|
||||
o.ManagedFields = cast
|
||||
}
|
||||
}
|
||||
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
|
||||
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
|
||||
o.OwnerReferences = cast
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) GetCreatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) SetCreatedBy(createdBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) GetUpdateTimestamp() time.Time {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
|
||||
return parsed
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) SetUpdateTimestamp(updateTimestamp time.Time) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) GetUpdatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) SetUpdatedBy(updatedBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) Copy() resource.Object {
|
||||
return resource.CopyObject(o)
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) DeepCopy() *LogsDrilldownDefaults {
|
||||
cpy := &LogsDrilldownDefaults{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaults) DeepCopyInto(dst *LogsDrilldownDefaults) {
|
||||
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
|
||||
dst.TypeMeta.Kind = o.TypeMeta.Kind
|
||||
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
|
||||
o.Spec.DeepCopyInto(&dst.Spec)
|
||||
o.Status.DeepCopyInto(&dst.Status)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.Object = &LogsDrilldownDefaults{}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type LogsDrilldownDefaultsList struct {
|
||||
metav1.TypeMeta `json:",inline" yaml:",inline"`
|
||||
metav1.ListMeta `json:"metadata" yaml:"metadata"`
|
||||
Items []LogsDrilldownDefaults `json:"items" yaml:"items"`
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultsList) DeepCopyObject() runtime.Object {
|
||||
return o.Copy()
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultsList) Copy() resource.ListObject {
|
||||
cpy := &LogsDrilldownDefaultsList{
|
||||
TypeMeta: o.TypeMeta,
|
||||
Items: make([]LogsDrilldownDefaults, len(o.Items)),
|
||||
}
|
||||
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
|
||||
for i := 0; i < len(o.Items); i++ {
|
||||
if item, ok := o.Items[i].Copy().(*LogsDrilldownDefaults); ok {
|
||||
cpy.Items[i] = *item
|
||||
}
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultsList) GetItems() []resource.Object {
|
||||
items := make([]resource.Object, len(o.Items))
|
||||
for i := 0; i < len(o.Items); i++ {
|
||||
items[i] = &o.Items[i]
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultsList) SetItems(items []resource.Object) {
|
||||
o.Items = make([]LogsDrilldownDefaults, len(items))
|
||||
for i := 0; i < len(items); i++ {
|
||||
o.Items[i] = *items[i].(*LogsDrilldownDefaults)
|
||||
}
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultsList) DeepCopy() *LogsDrilldownDefaultsList {
|
||||
cpy := &LogsDrilldownDefaultsList{}
|
||||
o.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
func (o *LogsDrilldownDefaultsList) DeepCopyInto(dst *LogsDrilldownDefaultsList) {
|
||||
resource.CopyObjectInto(dst, o)
|
||||
}
|
||||
|
||||
// Interface compliance compile-time check
|
||||
var _ resource.ListObject = &LogsDrilldownDefaultsList{}
|
||||
|
||||
// Copy methods for all subresource types
|
||||
|
||||
// DeepCopy creates a full deep copy of Spec
|
||||
func (s *Spec) DeepCopy() *Spec {
|
||||
cpy := &Spec{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies Spec into another Spec object
|
||||
func (s *Spec) DeepCopyInto(dst *Spec) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
|
||||
// DeepCopy creates a full deep copy of Status
|
||||
func (s *Status) DeepCopy() *Status {
|
||||
cpy := &Status{}
|
||||
s.DeepCopyInto(cpy)
|
||||
return cpy
|
||||
}
|
||||
|
||||
// DeepCopyInto deep copies Status into another Status object
|
||||
func (s *Status) DeepCopyInto(dst *Status) {
|
||||
resource.CopyObjectInto(dst, s)
|
||||
}
|
||||
Generated
-34
@@ -1,34 +0,0 @@
|
||||
//
|
||||
// Code generated by grafana-app-sdk. DO NOT EDIT.
|
||||
//
|
||||
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
)
|
||||
|
||||
// schema is unexported to prevent accidental overwrites
|
||||
var (
|
||||
schemaLogsDrilldownDefaults = resource.NewSimpleSchema("logsdrilldown.grafana.app", "v1alpha1", &LogsDrilldownDefaults{}, &LogsDrilldownDefaultsList{}, resource.WithKind("LogsDrilldownDefaults"),
|
||||
resource.WithPlural("logsdrilldowndefaults"), resource.WithScope(resource.NamespacedScope))
|
||||
kindLogsDrilldownDefaults = resource.Kind{
|
||||
Schema: schemaLogsDrilldownDefaults,
|
||||
Codecs: map[resource.KindEncoding]resource.Codec{
|
||||
resource.KindEncodingJSON: &JSONCodec{},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
// Kind returns a resource.Kind for this Schema with a JSON codec
|
||||
func Kind() resource.Kind {
|
||||
return kindLogsDrilldownDefaults
|
||||
}
|
||||
|
||||
// Schema returns a resource.SimpleSchema representation of LogsDrilldownDefaults
|
||||
func Schema() *resource.SimpleSchema {
|
||||
return schemaLogsDrilldownDefaults
|
||||
}
|
||||
|
||||
// Interface compliance checks
|
||||
var _ resource.Schema = kindLogsDrilldownDefaults
|
||||
Generated
-18
@@ -1,18 +0,0 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type Spec struct {
|
||||
DefaultFields []string `json:"defaultFields"`
|
||||
PrettifyJSON bool `json:"prettifyJSON"`
|
||||
WrapLogMessage bool `json:"wrapLogMessage"`
|
||||
InterceptDismissed bool `json:"interceptDismissed"`
|
||||
}
|
||||
|
||||
// NewSpec creates a new Spec object.
|
||||
func NewSpec() *Spec {
|
||||
return &Spec{
|
||||
DefaultFields: []string{},
|
||||
}
|
||||
}
|
||||
Generated
-44
@@ -1,44 +0,0 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
package v1alpha1
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StatusOperatorState struct {
|
||||
// lastEvaluation is the ResourceVersion last evaluated
|
||||
LastEvaluation string `json:"lastEvaluation"`
|
||||
// state describes the state of the lastEvaluation.
|
||||
// It is limited to three possible states for machine evaluation.
|
||||
State StatusOperatorStateState `json:"state"`
|
||||
// descriptiveState is an optional more descriptive state field which has no requirements on format
|
||||
DescriptiveState *string `json:"descriptiveState,omitempty"`
|
||||
// details contains any extra information that is operator-specific
|
||||
Details map[string]interface{} `json:"details,omitempty"`
|
||||
}
|
||||
|
||||
// NewStatusOperatorState creates a new StatusOperatorState object.
|
||||
func NewStatusOperatorState() *StatusOperatorState {
|
||||
return &StatusOperatorState{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type Status struct {
|
||||
// operatorStates is a map of operator ID to operator state evaluations.
|
||||
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
||||
OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"`
|
||||
// additionalFields is reserved for future use
|
||||
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
|
||||
}
|
||||
|
||||
// NewStatus creates a new Status object.
|
||||
func NewStatus() *Status {
|
||||
return &Status{}
|
||||
}
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type StatusOperatorStateState string
|
||||
|
||||
const (
|
||||
StatusOperatorStateStateSuccess StatusOperatorStateState = "success"
|
||||
StatusOperatorStateStateInProgress StatusOperatorStateState = "in_progress"
|
||||
StatusOperatorStateStateFailed StatusOperatorStateState = "failed"
|
||||
)
|
||||
@@ -1,150 +0,0 @@
|
||||
//
|
||||
// This file is generated by grafana-app-sdk
|
||||
// DO NOT EDIT
|
||||
//
|
||||
|
||||
package manifestdata
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/app"
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||||
|
||||
logsdrilldownv1alpha1 "github.com/grafana/grafana/apps/logsdrilldown/pkg/generated/logsdrilldown/v1alpha1"
|
||||
logsdrilldowndefaultcolumnsv1alpha1 "github.com/grafana/grafana/apps/logsdrilldown/pkg/generated/logsdrilldowndefaultcolumns/v1alpha1"
|
||||
logsdrilldowndefaultsv1alpha1 "github.com/grafana/grafana/apps/logsdrilldown/pkg/generated/logsdrilldowndefaults/v1alpha1"
|
||||
)
|
||||
|
||||
var (
|
||||
rawSchemaLogsDrilldownv1alpha1 = []byte(`{"LogsDrilldown":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"defaultFields":{"items":{"type":"string"},"type":"array"},"interceptDismissed":{"type":"boolean"},"prettifyJSON":{"type":"boolean"},"wrapLogMessage":{"type":"boolean"}},"required":["defaultFields","prettifyJSON","wrapLogMessage","interceptDismissed"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||
versionSchemaLogsDrilldownv1alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaLogsDrilldownv1alpha1, &versionSchemaLogsDrilldownv1alpha1)
|
||||
rawSchemaLogsDrilldownDefaultsv1alpha1 = []byte(`{"LogsDrilldownDefaults":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"defaultFields":{"items":{"type":"string"},"type":"array"},"interceptDismissed":{"type":"boolean"},"prettifyJSON":{"type":"boolean"},"wrapLogMessage":{"type":"boolean"}},"required":["defaultFields","prettifyJSON","wrapLogMessage","interceptDismissed"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||
versionSchemaLogsDrilldownDefaultsv1alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaLogsDrilldownDefaultsv1alpha1, &versionSchemaLogsDrilldownDefaultsv1alpha1)
|
||||
rawSchemaLogsDrilldownDefaultColumnsv1alpha1 = []byte(`{"LogsDefaultColumnsLabel":{"additionalProperties":false,"properties":{"key":{"type":"string"},"value":{"type":"string"}},"required":["key","value"],"type":"object"},"LogsDefaultColumnsLabels":{"items":{"$ref":"#/components/schemas/LogsDefaultColumnsLabel"},"type":"array"},"LogsDefaultColumnsRecord":{"additionalProperties":false,"properties":{"columns":{"items":{"type":"string"},"type":"array"},"labels":{"$ref":"#/components/schemas/LogsDefaultColumnsLabels"}},"required":["columns","labels"],"type":"object"},"LogsDefaultColumnsRecords":{"items":{"$ref":"#/components/schemas/LogsDefaultColumnsRecord"},"type":"array"},"LogsDrilldownDefaultColumns":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"records":{"$ref":"#/components/schemas/LogsDefaultColumnsRecords"}},"required":["records"],"type":"object"},"status":{"additionalProperties":false,"properties":{"additionalFields":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"additionalFields is reserved for future use","type":"object"},"operatorStates":{"additionalProperties":{"$ref":"#/components/schemas/OperatorState"},"description":"operatorStates is a map of operator ID to operator state evaluations.\nAny operator which consumes this kind SHOULD add its state evaluation information to this field.","type":"object"}},"type":"object"}}`)
|
||||
versionSchemaLogsDrilldownDefaultColumnsv1alpha1 app.VersionSchema
|
||||
_ = json.Unmarshal(rawSchemaLogsDrilldownDefaultColumnsv1alpha1, &versionSchemaLogsDrilldownDefaultColumnsv1alpha1)
|
||||
)
|
||||
|
||||
var appManifestData = app.ManifestData{
|
||||
AppName: "logsdrilldown",
|
||||
Group: "logsdrilldown.grafana.app",
|
||||
PreferredVersion: "v1alpha1",
|
||||
Versions: []app.ManifestVersion{
|
||||
{
|
||||
Name: "v1alpha1",
|
||||
Served: true,
|
||||
Kinds: []app.ManifestVersionKind{
|
||||
{
|
||||
Kind: "LogsDrilldown",
|
||||
Plural: "LogsDrilldowns",
|
||||
Scope: "Namespaced",
|
||||
Conversion: false,
|
||||
Schema: &versionSchemaLogsDrilldownv1alpha1,
|
||||
},
|
||||
|
||||
{
|
||||
Kind: "LogsDrilldownDefaults",
|
||||
Plural: "LogsDrilldownDefaults",
|
||||
Scope: "Namespaced",
|
||||
Conversion: false,
|
||||
Schema: &versionSchemaLogsDrilldownDefaultsv1alpha1,
|
||||
},
|
||||
|
||||
{
|
||||
Kind: "LogsDrilldownDefaultColumns",
|
||||
Plural: "LogsDrilldownDefaultColumns",
|
||||
Scope: "Namespaced",
|
||||
Conversion: false,
|
||||
Schema: &versionSchemaLogsDrilldownDefaultColumnsv1alpha1,
|
||||
},
|
||||
},
|
||||
Routes: app.ManifestVersionRoutes{
|
||||
Namespaced: map[string]spec3.PathProps{},
|
||||
Cluster: map[string]spec3.PathProps{},
|
||||
Schemas: map[string]spec.Schema{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func LocalManifest() app.Manifest {
|
||||
return app.NewEmbeddedManifest(appManifestData)
|
||||
}
|
||||
|
||||
func RemoteManifest() app.Manifest {
|
||||
return app.NewAPIServerManifest("logsdrilldown")
|
||||
}
|
||||
|
||||
var kindVersionToGoType = map[string]resource.Kind{
|
||||
"LogsDrilldown/v1alpha1": logsdrilldownv1alpha1.Kind(),
|
||||
"LogsDrilldownDefaults/v1alpha1": logsdrilldowndefaultsv1alpha1.Kind(),
|
||||
"LogsDrilldownDefaultColumns/v1alpha1": logsdrilldowndefaultcolumnsv1alpha1.Kind(),
|
||||
}
|
||||
|
||||
// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists.
|
||||
// If there is no association for the provided Kind and Version, exists will return false.
|
||||
func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exists bool) {
|
||||
goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)]
|
||||
return goType, exists
|
||||
}
|
||||
|
||||
var customRouteToGoResponseType = map[string]any{}
|
||||
|
||||
// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists.
|
||||
// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths.
|
||||
// If there is no association for the provided kind, version, custom route path, and method, exists will return false.
|
||||
// Resource routes (those without a kind) should prefix their route with "<namespace>/" if the route is namespaced (otherwise the route is assumed to be cluster-scope)
|
||||
func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) {
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
|
||||
return goType, exists
|
||||
}
|
||||
|
||||
var customRouteToGoParamsType = map[string]runtime.Object{}
|
||||
|
||||
func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goType runtime.Object, exists bool) {
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
goType, exists = customRouteToGoParamsType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
|
||||
return goType, exists
|
||||
}
|
||||
|
||||
var customRouteToGoRequestBodyType = map[string]any{}
|
||||
|
||||
func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) {
|
||||
if len(path) > 0 && path[0] == '/' {
|
||||
path = path[1:]
|
||||
}
|
||||
goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
|
||||
return goType, exists
|
||||
}
|
||||
|
||||
type GoTypeAssociator struct{}
|
||||
|
||||
func NewGoTypeAssociator() *GoTypeAssociator {
|
||||
return &GoTypeAssociator{}
|
||||
}
|
||||
|
||||
func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) {
|
||||
return ManifestGoTypeAssociator(kind, version)
|
||||
}
|
||||
func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb string) (goType any, exists bool) {
|
||||
return ManifestCustomRouteResponsesAssociator(kind, version, path, verb)
|
||||
}
|
||||
func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) {
|
||||
return ManifestCustomRouteQueryAssociator(kind, version, path, verb)
|
||||
}
|
||||
func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) {
|
||||
return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb)
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file was generated by grafana-app-sdk. DO NOT EDIT.
|
||||
*/
|
||||
import { Spec } from './types.spec.gen';
|
||||
import { Status } from './types.status.gen';
|
||||
|
||||
export interface Metadata {
|
||||
name: string;
|
||||
namespace: string;
|
||||
generateName?: string;
|
||||
selfLink?: string;
|
||||
uid?: string;
|
||||
resourceVersion?: string;
|
||||
generation?: number;
|
||||
creationTimestamp?: string;
|
||||
deletionTimestamp?: string;
|
||||
deletionGracePeriodSeconds?: number;
|
||||
labels?: Record<string, string>;
|
||||
annotations?: Record<string, string>;
|
||||
ownerReferences?: OwnerReference[];
|
||||
finalizers?: string[];
|
||||
managedFields?: ManagedFieldsEntry[];
|
||||
}
|
||||
|
||||
export interface OwnerReference {
|
||||
apiVersion: string;
|
||||
kind: string;
|
||||
name: string;
|
||||
uid: string;
|
||||
controller?: boolean;
|
||||
blockOwnerDeletion?: boolean;
|
||||
}
|
||||
|
||||
export interface ManagedFieldsEntry {
|
||||
manager?: string;
|
||||
operation?: string;
|
||||
apiVersion?: string;
|
||||
time?: string;
|
||||
fieldsType?: string;
|
||||
subresource?: string;
|
||||
}
|
||||
|
||||
export interface LogsDrilldownDefaultColumns {
|
||||
kind: string;
|
||||
apiVersion: string;
|
||||
metadata: Metadata;
|
||||
spec: Spec;
|
||||
status: Status;
|
||||
}
|
||||
Generated
+30
@@ -0,0 +1,30 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
// metadata contains embedded CommonMetadata and can be extended with custom string fields
|
||||
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
|
||||
// without external reference as using the CommonMetadata reference breaks thema codegen.
|
||||
export interface Metadata {
|
||||
updateTimestamp: string;
|
||||
createdBy: string;
|
||||
uid: string;
|
||||
creationTimestamp: string;
|
||||
deletionTimestamp?: string;
|
||||
finalizers: string[];
|
||||
resourceVersion: string;
|
||||
generation: number;
|
||||
updatedBy: string;
|
||||
labels: Record<string, string>;
|
||||
}
|
||||
|
||||
export const defaultMetadata = (): Metadata => ({
|
||||
updateTimestamp: "",
|
||||
createdBy: "",
|
||||
uid: "",
|
||||
creationTimestamp: "",
|
||||
finalizers: [],
|
||||
resourceVersion: "",
|
||||
generation: 0,
|
||||
updatedBy: "",
|
||||
labels: {},
|
||||
});
|
||||
|
||||
Generated
+38
@@ -0,0 +1,38 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
export type LogsDefaultColumnsRecords = LogsDefaultColumnsRecord[];
|
||||
|
||||
export const defaultLogsDefaultColumnsRecords = (): LogsDefaultColumnsRecords => ([]);
|
||||
|
||||
export interface LogsDefaultColumnsRecord {
|
||||
columns: string[];
|
||||
labels: LogsDefaultColumnsLabels;
|
||||
}
|
||||
|
||||
export const defaultLogsDefaultColumnsRecord = (): LogsDefaultColumnsRecord => ({
|
||||
columns: [],
|
||||
labels: defaultLogsDefaultColumnsLabels(),
|
||||
});
|
||||
|
||||
export type LogsDefaultColumnsLabels = LogsDefaultColumnsLabel[];
|
||||
|
||||
export const defaultLogsDefaultColumnsLabels = (): LogsDefaultColumnsLabels => ([]);
|
||||
|
||||
export interface LogsDefaultColumnsLabel {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const defaultLogsDefaultColumnsLabel = (): LogsDefaultColumnsLabel => ({
|
||||
key: "",
|
||||
value: "",
|
||||
});
|
||||
|
||||
export interface Spec {
|
||||
records: LogsDefaultColumnsRecords;
|
||||
}
|
||||
|
||||
export const defaultSpec = (): Spec => ({
|
||||
records: defaultLogsDefaultColumnsRecords(),
|
||||
});
|
||||
|
||||
Generated
+30
@@ -0,0 +1,30 @@
|
||||
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
|
||||
|
||||
export interface OperatorState {
|
||||
// lastEvaluation is the ResourceVersion last evaluated
|
||||
lastEvaluation: string;
|
||||
// state describes the state of the lastEvaluation.
|
||||
// It is limited to three possible states for machine evaluation.
|
||||
state: "success" | "in_progress" | "failed";
|
||||
// descriptiveState is an optional more descriptive state field which has no requirements on format
|
||||
descriptiveState?: string;
|
||||
// details contains any extra information that is operator-specific
|
||||
details?: Record<string, any>;
|
||||
}
|
||||
|
||||
export const defaultOperatorState = (): OperatorState => ({
|
||||
lastEvaluation: "",
|
||||
state: "success",
|
||||
});
|
||||
|
||||
export interface Status {
|
||||
// operatorStates is a map of operator ID to operator state evaluations.
|
||||
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
|
||||
operatorStates?: Record<string, OperatorState>;
|
||||
// additionalFields is reserved for future use
|
||||
additionalFields?: Record<string, any>;
|
||||
}
|
||||
|
||||
export const defaultStatus = (): Status => ({
|
||||
});
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ require (
|
||||
github.com/google/gnostic-models v0.7.1 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f // indirect
|
||||
github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f // indirect
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect
|
||||
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 // indirect
|
||||
github.com/grafana/dataplane/sdata v0.0.9 // indirect
|
||||
|
||||
+2
-2
@@ -215,8 +215,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f h1:Br4SaUL3dnVopKKNhDavCLgehw60jdtl/sIxdfzmVts=
|
||||
github.com/grafana/alerting v0.0.0-20251231150637-b7821017d69f/go.mod h1:l7v67cgP7x72ajB9UPZlumdrHqNztpKoqQ52cU8T3LU=
|
||||
github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f h1:3bXOyht68qkfvD6Y8z8XoenFbytSSOIkr/s+AqRzj0o=
|
||||
github.com/grafana/alerting v0.0.0-20260112172717-98a49ed9557f/go.mod h1:Ji0SfJChcwjgq8ljy6Y5CcYfHfAYKXjKYeysOoDS/6s=
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f h1:Cbm6OKkOcJ+7CSZsGsEJzktC/SIa5bxVeYKQLuYK86o=
|
||||
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f/go.mod h1:axY0cdOg3q0TZHwpHnIz5x16xZ8ZBxJHShsSHHXcHQg=
|
||||
github.com/grafana/authlib/types v0.0.0-20251119142549-be091cf2f4d4 h1:Muoy+FMGrHj3GdFbvsMzUT7eusgii9PKf9L1ZaXDDbY=
|
||||
|
||||
@@ -32,7 +32,7 @@ type ConnectionSecure struct {
|
||||
|
||||
// Token is the reference of the token used to act as the Connection.
|
||||
// This value is stored securely and cannot be read back
|
||||
Token common.InlineSecureValue `json:"webhook,omitzero,omitempty"`
|
||||
Token common.InlineSecureValue `json:"token,omitzero,omitempty"`
|
||||
}
|
||||
|
||||
func (v ConnectionSecure) IsZero() bool {
|
||||
|
||||
@@ -320,7 +320,7 @@ func schema_pkg_apis_provisioning_v0alpha1_ConnectionSecure(ref common.Reference
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.InlineSecureValue"),
|
||||
},
|
||||
},
|
||||
"webhook": {
|
||||
"token": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Token is the reference of the token used to act as the Connection. This value is stored securely and cannot be read back",
|
||||
Default: map[string]interface{}{},
|
||||
|
||||
-1
@@ -22,7 +22,6 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioni
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ResourceList,Items
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,TestResults,Errors
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,WebhookStatus,SubscribedEvents
|
||||
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionSecure,Token
|
||||
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,ConnectionSpec,GitHub
|
||||
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobSpec,PullRequest
|
||||
API rule violation: names_match,github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1,JobStatus,URLs
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package connection
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
//go:generate mockery --name Connection --structname MockConnection --inpackage --filename connection_mock.go --with-expecter
|
||||
type Connection interface {
|
||||
// Validate ensures the resource _looks_ correct.
|
||||
// It should be called before trying to upsert a resource into the Kubernetes API server.
|
||||
// This is not an indication that the connection information works, just that they are reasonably configured.
|
||||
Validate(ctx context.Context) error
|
||||
|
||||
// Mutate performs in place mutation of the underneath resource.
|
||||
Mutate(context.Context) error
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package connection
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockConnection is an autogenerated mock type for the Connection type
|
||||
type MockConnection struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockConnection_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockConnection) EXPECT() *MockConnection_Expecter {
|
||||
return &MockConnection_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Mutate provides a mock function with given fields: _a0
|
||||
func (_m *MockConnection) Mutate(_a0 context.Context) error {
|
||||
ret := _m.Called(_a0)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Mutate")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||
r0 = rf(_a0)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockConnection_Mutate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Mutate'
|
||||
type MockConnection_Mutate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Mutate is a helper method to define mock.On call
|
||||
// - _a0 context.Context
|
||||
func (_e *MockConnection_Expecter) Mutate(_a0 interface{}) *MockConnection_Mutate_Call {
|
||||
return &MockConnection_Mutate_Call{Call: _e.mock.On("Mutate", _a0)}
|
||||
}
|
||||
|
||||
func (_c *MockConnection_Mutate_Call) Run(run func(_a0 context.Context)) *MockConnection_Mutate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConnection_Mutate_Call) Return(_a0 error) *MockConnection_Mutate_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConnection_Mutate_Call) RunAndReturn(run func(context.Context) error) *MockConnection_Mutate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Validate provides a mock function with given fields: ctx
|
||||
func (_m *MockConnection) Validate(ctx context.Context) error {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Validate")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockConnection_Validate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Validate'
|
||||
type MockConnection_Validate_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Validate is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockConnection_Expecter) Validate(ctx interface{}) *MockConnection_Validate_Call {
|
||||
return &MockConnection_Validate_Call{Call: _e.mock.On("Validate", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockConnection_Validate_Call) Run(run func(ctx context.Context)) *MockConnection_Validate_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConnection_Validate_Call) Return(_a0 error) *MockConnection_Validate_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockConnection_Validate_Call) RunAndReturn(run func(context.Context) error) *MockConnection_Validate_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockConnection creates a new instance of MockConnection. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockConnection(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockConnection {
|
||||
mock := &MockConnection{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package connection
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockExtra is an autogenerated mock type for the Extra type
|
||||
type MockExtra struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockExtra_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockExtra) EXPECT() *MockExtra_Expecter {
|
||||
return &MockExtra_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Build provides a mock function with given fields: ctx, r
|
||||
func (_m *MockExtra) Build(ctx context.Context, r *v0alpha1.Connection) (Connection, error) {
|
||||
ret := _m.Called(ctx, r)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Build")
|
||||
}
|
||||
|
||||
var r0 Connection
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) (Connection, error)); ok {
|
||||
return rf(ctx, r)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) Connection); ok {
|
||||
r0 = rf(ctx, r)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(Connection)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *v0alpha1.Connection) error); ok {
|
||||
r1 = rf(ctx, r)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockExtra_Build_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Build'
|
||||
type MockExtra_Build_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Build is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - r *v0alpha1.Connection
|
||||
func (_e *MockExtra_Expecter) Build(ctx interface{}, r interface{}) *MockExtra_Build_Call {
|
||||
return &MockExtra_Build_Call{Call: _e.mock.On("Build", ctx, r)}
|
||||
}
|
||||
|
||||
func (_c *MockExtra_Build_Call) Run(run func(ctx context.Context, r *v0alpha1.Connection)) *MockExtra_Build_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(*v0alpha1.Connection))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockExtra_Build_Call) Return(_a0 Connection, _a1 error) *MockExtra_Build_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockExtra_Build_Call) RunAndReturn(run func(context.Context, *v0alpha1.Connection) (Connection, error)) *MockExtra_Build_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Type provides a mock function with no fields
|
||||
func (_m *MockExtra) Type() v0alpha1.ConnectionType {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Type")
|
||||
}
|
||||
|
||||
var r0 v0alpha1.ConnectionType
|
||||
if rf, ok := ret.Get(0).(func() v0alpha1.ConnectionType); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(v0alpha1.ConnectionType)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockExtra_Type_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Type'
|
||||
type MockExtra_Type_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Type is a helper method to define mock.On call
|
||||
func (_e *MockExtra_Expecter) Type() *MockExtra_Type_Call {
|
||||
return &MockExtra_Type_Call{Call: _e.mock.On("Type")}
|
||||
}
|
||||
|
||||
func (_c *MockExtra_Type_Call) Run(run func()) *MockExtra_Type_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockExtra_Type_Call) Return(_a0 v0alpha1.ConnectionType) *MockExtra_Type_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockExtra_Type_Call) RunAndReturn(run func() v0alpha1.ConnectionType) *MockExtra_Type_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockExtra creates a new instance of MockExtra. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockExtra(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockExtra {
|
||||
mock := &MockExtra{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package connection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
//go:generate mockery --name=Extra --structname=MockExtra --inpackage --filename=extra_mock.go --with-expecter
|
||||
type Extra interface {
|
||||
Type() provisioning.ConnectionType
|
||||
Build(ctx context.Context, r *provisioning.Connection) (Connection, error)
|
||||
}
|
||||
|
||||
//go:generate mockery --name=Factory --structname=MockFactory --inpackage --filename=factory_mock.go --with-expecter
|
||||
type Factory interface {
|
||||
Types() []provisioning.ConnectionType
|
||||
Build(ctx context.Context, r *provisioning.Connection) (Connection, error)
|
||||
}
|
||||
|
||||
type factory struct {
|
||||
extras map[provisioning.ConnectionType]Extra
|
||||
enabled map[provisioning.ConnectionType]struct{}
|
||||
}
|
||||
|
||||
func ProvideFactory(enabled map[provisioning.ConnectionType]struct{}, extras []Extra) (Factory, error) {
|
||||
f := &factory{
|
||||
enabled: enabled,
|
||||
extras: make(map[provisioning.ConnectionType]Extra, len(extras)),
|
||||
}
|
||||
|
||||
for _, e := range extras {
|
||||
if _, exists := f.extras[e.Type()]; exists {
|
||||
return nil, fmt.Errorf("connection type %q is already registered", e.Type())
|
||||
}
|
||||
f.extras[e.Type()] = e
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (f *factory) Types() []provisioning.ConnectionType {
|
||||
var types []provisioning.ConnectionType
|
||||
for t := range f.enabled {
|
||||
if _, exists := f.extras[t]; exists {
|
||||
types = append(types, t)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(types, func(i, j int) bool {
|
||||
return string(types[i]) < string(types[j])
|
||||
})
|
||||
|
||||
return types
|
||||
}
|
||||
|
||||
func (f *factory) Build(ctx context.Context, c *provisioning.Connection) (Connection, error) {
|
||||
for _, e := range f.extras {
|
||||
if e.Type() == c.Spec.Type {
|
||||
if _, enabled := f.enabled[e.Type()]; !enabled {
|
||||
return nil, fmt.Errorf("connection type %q is not enabled", e.Type())
|
||||
}
|
||||
|
||||
return e.Build(ctx, c)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("connection type %q is not supported", c.Spec.Type)
|
||||
}
|
||||
|
||||
var (
|
||||
_ Factory = (*factory)(nil)
|
||||
)
|
||||
@@ -0,0 +1,143 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package connection
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockFactory is an autogenerated mock type for the Factory type
|
||||
type MockFactory struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockFactory_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockFactory) EXPECT() *MockFactory_Expecter {
|
||||
return &MockFactory_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// Build provides a mock function with given fields: ctx, r
|
||||
func (_m *MockFactory) Build(ctx context.Context, r *v0alpha1.Connection) (Connection, error) {
|
||||
ret := _m.Called(ctx, r)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Build")
|
||||
}
|
||||
|
||||
var r0 Connection
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) (Connection, error)); ok {
|
||||
return rf(ctx, r)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Connection) Connection); ok {
|
||||
r0 = rf(ctx, r)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(Connection)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, *v0alpha1.Connection) error); ok {
|
||||
r1 = rf(ctx, r)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockFactory_Build_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Build'
|
||||
type MockFactory_Build_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Build is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - r *v0alpha1.Connection
|
||||
func (_e *MockFactory_Expecter) Build(ctx interface{}, r interface{}) *MockFactory_Build_Call {
|
||||
return &MockFactory_Build_Call{Call: _e.mock.On("Build", ctx, r)}
|
||||
}
|
||||
|
||||
func (_c *MockFactory_Build_Call) Run(run func(ctx context.Context, r *v0alpha1.Connection)) *MockFactory_Build_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(*v0alpha1.Connection))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFactory_Build_Call) Return(_a0 Connection, _a1 error) *MockFactory_Build_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFactory_Build_Call) RunAndReturn(run func(context.Context, *v0alpha1.Connection) (Connection, error)) *MockFactory_Build_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// Types provides a mock function with no fields
|
||||
func (_m *MockFactory) Types() []v0alpha1.ConnectionType {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Types")
|
||||
}
|
||||
|
||||
var r0 []v0alpha1.ConnectionType
|
||||
if rf, ok := ret.Get(0).(func() []v0alpha1.ConnectionType); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]v0alpha1.ConnectionType)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockFactory_Types_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Types'
|
||||
type MockFactory_Types_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// Types is a helper method to define mock.On call
|
||||
func (_e *MockFactory_Expecter) Types() *MockFactory_Types_Call {
|
||||
return &MockFactory_Types_Call{Call: _e.mock.On("Types")}
|
||||
}
|
||||
|
||||
func (_c *MockFactory_Types_Call) Run(run func()) *MockFactory_Types_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFactory_Types_Call) Return(_a0 []v0alpha1.ConnectionType) *MockFactory_Types_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockFactory_Types_Call) RunAndReturn(run func() []v0alpha1.ConnectionType) *MockFactory_Types_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockFactory creates a new instance of MockFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockFactory(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockFactory {
|
||||
mock := &MockFactory{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package connection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestProvideFactory(t *testing.T) {
|
||||
t.Run("should create factory with valid extras", func(t *testing.T) {
|
||||
extra1 := NewMockExtra(t)
|
||||
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
|
||||
extra2 := NewMockExtra(t)
|
||||
extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
provisioning.GitlabConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, factory)
|
||||
})
|
||||
|
||||
t.Run("should create factory with empty extras", func(t *testing.T) {
|
||||
enabled := map[provisioning.ConnectionType]struct{}{}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, factory)
|
||||
})
|
||||
|
||||
t.Run("should create factory with nil enabled map", func(t *testing.T) {
|
||||
extra1 := NewMockExtra(t)
|
||||
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
|
||||
factory, err := ProvideFactory(nil, []Extra{extra1})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, factory)
|
||||
})
|
||||
|
||||
t.Run("should return error when duplicate repository types", func(t *testing.T) {
|
||||
extra1 := NewMockExtra(t)
|
||||
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
|
||||
extra2 := NewMockExtra(t)
|
||||
extra2.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, factory)
|
||||
assert.Contains(t, err.Error(), "connection type \"github\" is already registered")
|
||||
})
|
||||
}
|
||||
|
||||
func TestFactory_Types(t *testing.T) {
|
||||
t.Run("should return only enabled types that have extras", func(t *testing.T) {
|
||||
extra1 := NewMockExtra(t)
|
||||
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
|
||||
extra2 := NewMockExtra(t)
|
||||
extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
provisioning.GitlabConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
|
||||
require.NoError(t, err)
|
||||
|
||||
types := factory.Types()
|
||||
assert.Len(t, types, 2)
|
||||
assert.Contains(t, types, provisioning.GithubConnectionType)
|
||||
assert.Contains(t, types, provisioning.GitlabConnectionType)
|
||||
})
|
||||
|
||||
t.Run("should return sorted list of types", func(t *testing.T) {
|
||||
extra1 := NewMockExtra(t)
|
||||
extra1.EXPECT().Type().Return(provisioning.GitlabConnectionType)
|
||||
|
||||
extra2 := NewMockExtra(t)
|
||||
extra2.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
provisioning.GitlabConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
|
||||
require.NoError(t, err)
|
||||
|
||||
types := factory.Types()
|
||||
assert.Len(t, types, 2)
|
||||
// github should come before gitlab alphabetically
|
||||
assert.Equal(t, provisioning.GithubConnectionType, types[0])
|
||||
assert.Equal(t, provisioning.GitlabConnectionType, types[1])
|
||||
})
|
||||
|
||||
t.Run("should return empty list when no types are enabled", func(t *testing.T) {
|
||||
extra1 := NewMockExtra(t)
|
||||
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra1})
|
||||
require.NoError(t, err)
|
||||
|
||||
types := factory.Types()
|
||||
assert.Empty(t, types)
|
||||
})
|
||||
|
||||
t.Run("should not return types that are enabled but have no extras", func(t *testing.T) {
|
||||
extra1 := NewMockExtra(t)
|
||||
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
provisioning.GitlabConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra1})
|
||||
require.NoError(t, err)
|
||||
|
||||
types := factory.Types()
|
||||
assert.Len(t, types, 1)
|
||||
assert.Contains(t, types, provisioning.GithubConnectionType)
|
||||
assert.NotContains(t, types, provisioning.GitlabConnectionType)
|
||||
})
|
||||
|
||||
t.Run("should not return types that have extras but are not enabled", func(t *testing.T) {
|
||||
extra1 := NewMockExtra(t)
|
||||
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
|
||||
extra2 := NewMockExtra(t)
|
||||
extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
|
||||
require.NoError(t, err)
|
||||
|
||||
types := factory.Types()
|
||||
assert.Len(t, types, 1)
|
||||
assert.Contains(t, types, provisioning.GithubConnectionType)
|
||||
assert.NotContains(t, types, provisioning.GitlabConnectionType)
|
||||
})
|
||||
|
||||
t.Run("should return empty list when no extras are provided", func(t *testing.T) {
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{})
|
||||
require.NoError(t, err)
|
||||
|
||||
types := factory.Types()
|
||||
assert.Empty(t, types)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFactory_Build(t *testing.T) {
|
||||
t.Run("should successfully build connection when type is enabled and has extra", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
},
|
||||
}
|
||||
|
||||
mockConnection := NewMockConnection(t)
|
||||
extra := NewMockExtra(t)
|
||||
extra.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
extra.EXPECT().Build(ctx, conn).Return(mockConnection, nil)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra})
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := factory.Build(ctx, conn)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, mockConnection, result)
|
||||
})
|
||||
|
||||
t.Run("should return error when type is not enabled", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GitlabConnectionType,
|
||||
},
|
||||
}
|
||||
|
||||
extra := NewMockExtra(t)
|
||||
extra.EXPECT().Type().Return(provisioning.GitlabConnectionType)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra})
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := factory.Build(ctx, conn)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, result)
|
||||
assert.Contains(t, err.Error(), "connection type \"gitlab\" is not enabled")
|
||||
})
|
||||
|
||||
t.Run("should return error when type is not supported", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GitlabConnectionType,
|
||||
},
|
||||
}
|
||||
|
||||
extra := NewMockExtra(t)
|
||||
extra.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra})
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := factory.Build(ctx, conn)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, result)
|
||||
assert.Contains(t, err.Error(), "connection type \"gitlab\" is not supported")
|
||||
})
|
||||
|
||||
t.Run("should pass through errors from extra.Build()", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
},
|
||||
}
|
||||
|
||||
expectedErr := errors.New("build error")
|
||||
extra := NewMockExtra(t)
|
||||
extra.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
extra.EXPECT().Build(ctx, conn).Return(nil, expectedErr)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra})
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := factory.Build(ctx, conn)
|
||||
require.Error(t, err)
|
||||
assert.Nil(t, result)
|
||||
assert.Equal(t, expectedErr, err)
|
||||
})
|
||||
|
||||
t.Run("should build with multiple extras registered", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GitlabConnectionType,
|
||||
},
|
||||
}
|
||||
|
||||
mockConnection := NewMockConnection(t)
|
||||
|
||||
extra1 := NewMockExtra(t)
|
||||
extra1.EXPECT().Type().Return(provisioning.GithubConnectionType)
|
||||
|
||||
extra2 := NewMockExtra(t)
|
||||
extra2.EXPECT().Type().Return(provisioning.GitlabConnectionType)
|
||||
extra2.EXPECT().Build(ctx, conn).Return(mockConnection, nil)
|
||||
|
||||
enabled := map[provisioning.ConnectionType]struct{}{
|
||||
provisioning.GithubConnectionType: {},
|
||||
provisioning.GitlabConnectionType: {},
|
||||
}
|
||||
|
||||
factory, err := ProvideFactory(enabled, []Extra{extra1, extra2})
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := factory.Build(ctx, conn)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, mockConnection, result)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/go-github/v70/github"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
// API errors that we need to convey after parsing real GH errors (or faking them).
|
||||
var (
|
||||
//lint:ignore ST1005 this is not punctuation
|
||||
ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable")
|
||||
)
|
||||
|
||||
//go:generate mockery --name Client --structname MockClient --inpackage --filename client_mock.go --with-expecter
|
||||
type Client interface {
|
||||
// Apps and installations
|
||||
GetApp(ctx context.Context) (App, error)
|
||||
GetAppInstallation(ctx context.Context, installationID string) (AppInstallation, error)
|
||||
}
|
||||
|
||||
// App represents a Github App.
|
||||
type App struct {
|
||||
// ID represents the GH app ID.
|
||||
ID int64
|
||||
// Slug represents the GH app slug.
|
||||
Slug string
|
||||
// Owner represents the GH account/org owning the app
|
||||
Owner string
|
||||
}
|
||||
|
||||
// AppInstallation represents a Github App Installation.
|
||||
type AppInstallation struct {
|
||||
// ID represents the GH installation ID.
|
||||
ID int64
|
||||
// Whether the installation is enabled or not.
|
||||
Enabled bool
|
||||
}
|
||||
|
||||
type githubClient struct {
|
||||
gh *github.Client
|
||||
}
|
||||
|
||||
func NewClient(client *github.Client) Client {
|
||||
return &githubClient{client}
|
||||
}
|
||||
|
||||
// GetApp gets the app by using the given token.
|
||||
func (r *githubClient) GetApp(ctx context.Context) (App, error) {
|
||||
app, _, err := r.gh.Apps.Get(ctx, "")
|
||||
if err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return App{}, ErrServiceUnavailable
|
||||
}
|
||||
return App{}, err
|
||||
}
|
||||
|
||||
// TODO(ferruvich): do we need any other info?
|
||||
return App{
|
||||
ID: app.GetID(),
|
||||
Slug: app.GetSlug(),
|
||||
Owner: app.GetOwner().GetLogin(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAppInstallation gets the installation of the app related to the given token.
|
||||
func (r *githubClient) GetAppInstallation(ctx context.Context, installationID string) (AppInstallation, error) {
|
||||
id, err := strconv.Atoi(installationID)
|
||||
if err != nil {
|
||||
return AppInstallation{}, fmt.Errorf("invalid installation ID: %s", installationID)
|
||||
}
|
||||
|
||||
installation, _, err := r.gh.Apps.GetInstallation(ctx, int64(id))
|
||||
if err != nil {
|
||||
var ghErr *github.ErrorResponse
|
||||
if errors.As(err, &ghErr) && ghErr.Response.StatusCode == http.StatusServiceUnavailable {
|
||||
return AppInstallation{}, ErrServiceUnavailable
|
||||
}
|
||||
return AppInstallation{}, err
|
||||
}
|
||||
|
||||
// TODO(ferruvich): do we need any other info?
|
||||
return AppInstallation{
|
||||
ID: installation.GetID(),
|
||||
Enabled: installation.GetSuspendedAt().IsZero(),
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockClient is an autogenerated mock type for the Client type
|
||||
type MockClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockClient_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockClient) EXPECT() *MockClient_Expecter {
|
||||
return &MockClient_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// GetApp provides a mock function with given fields: ctx
|
||||
func (_m *MockClient) GetApp(ctx context.Context) (App, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetApp")
|
||||
}
|
||||
|
||||
var r0 App
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) (App, error)); ok {
|
||||
return rf(ctx)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context) App); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
r0 = ret.Get(0).(App)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClient_GetApp_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetApp'
|
||||
type MockClient_GetApp_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetApp is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *MockClient_Expecter) GetApp(ctx interface{}) *MockClient_GetApp_Call {
|
||||
return &MockClient_GetApp_Call{Call: _e.mock.On("GetApp", ctx)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetApp_Call) Run(run func(ctx context.Context)) *MockClient_GetApp_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetApp_Call) Return(_a0 App, _a1 error) *MockClient_GetApp_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetApp_Call) RunAndReturn(run func(context.Context) (App, error)) *MockClient_GetApp_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetAppInstallation provides a mock function with given fields: ctx, installationID
|
||||
func (_m *MockClient) GetAppInstallation(ctx context.Context, installationID string) (AppInstallation, error) {
|
||||
ret := _m.Called(ctx, installationID)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetAppInstallation")
|
||||
}
|
||||
|
||||
var r0 AppInstallation
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) (AppInstallation, error)); ok {
|
||||
return rf(ctx, installationID)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) AppInstallation); ok {
|
||||
r0 = rf(ctx, installationID)
|
||||
} else {
|
||||
r0 = ret.Get(0).(AppInstallation)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, installationID)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// MockClient_GetAppInstallation_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAppInstallation'
|
||||
type MockClient_GetAppInstallation_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetAppInstallation is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - installationID string
|
||||
func (_e *MockClient_Expecter) GetAppInstallation(ctx interface{}, installationID interface{}) *MockClient_GetAppInstallation_Call {
|
||||
return &MockClient_GetAppInstallation_Call{Call: _e.mock.On("GetAppInstallation", ctx, installationID)}
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetAppInstallation_Call) Run(run func(ctx context.Context, installationID string)) *MockClient_GetAppInstallation_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetAppInstallation_Call) Return(_a0 AppInstallation, _a1 error) *MockClient_GetAppInstallation_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockClient_GetAppInstallation_Call) RunAndReturn(run func(context.Context, string) (AppInstallation, error)) *MockClient_GetAppInstallation_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockClient creates a new instance of MockClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockClient(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockClient {
|
||||
mock := &MockClient{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package github_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v70/github"
|
||||
conngh "github.com/grafana/grafana/apps/provisioning/pkg/connection/github"
|
||||
mockhub "github.com/migueleliasweb/go-github-mock/src/mock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGithubClient_GetApp(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mockHandler *http.Client
|
||||
token string
|
||||
wantApp conngh.App
|
||||
wantErr error
|
||||
}{
|
||||
{
|
||||
name: "get app successfully",
|
||||
mockHandler: mockhub.NewMockedHTTPClient(
|
||||
mockhub.WithRequestMatchHandler(
|
||||
mockhub.GetApp,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
app := &github.App{
|
||||
ID: github.Ptr(int64(12345)),
|
||||
Slug: github.Ptr("my-test-app"),
|
||||
Owner: &github.User{
|
||||
Login: github.Ptr("grafana"),
|
||||
},
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(app))
|
||||
}),
|
||||
),
|
||||
),
|
||||
token: "test-token",
|
||||
wantApp: conngh.App{
|
||||
ID: 12345,
|
||||
Slug: "my-test-app",
|
||||
Owner: "grafana",
|
||||
},
|
||||
wantErr: nil,
|
||||
},
|
||||
{
|
||||
name: "service unavailable",
|
||||
mockHandler: mockhub.NewMockedHTTPClient(
|
||||
mockhub.WithRequestMatchHandler(
|
||||
mockhub.GetApp,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
||||
Response: &http.Response{
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
},
|
||||
Message: "Service unavailable",
|
||||
}))
|
||||
}),
|
||||
),
|
||||
),
|
||||
token: "test-token",
|
||||
wantApp: conngh.App{},
|
||||
wantErr: conngh.ErrServiceUnavailable,
|
||||
},
|
||||
{
|
||||
name: "other error",
|
||||
mockHandler: mockhub.NewMockedHTTPClient(
|
||||
mockhub.WithRequestMatchHandler(
|
||||
mockhub.GetApp,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
||||
Response: &http.Response{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
},
|
||||
Message: "Internal server error",
|
||||
}))
|
||||
}),
|
||||
),
|
||||
),
|
||||
token: "test-token",
|
||||
wantApp: conngh.App{},
|
||||
wantErr: &github.ErrorResponse{
|
||||
Response: &http.Response{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
},
|
||||
Message: "Internal server error",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "unauthorized error",
|
||||
mockHandler: mockhub.NewMockedHTTPClient(
|
||||
mockhub.WithRequestMatchHandler(
|
||||
mockhub.GetApp,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
||||
Response: &http.Response{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
},
|
||||
Message: "Bad credentials",
|
||||
}))
|
||||
}),
|
||||
),
|
||||
),
|
||||
token: "invalid-token",
|
||||
wantApp: conngh.App{},
|
||||
wantErr: &github.ErrorResponse{
|
||||
Response: &http.Response{
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
},
|
||||
Message: "Bad credentials",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create a mock client
|
||||
ghClient := github.NewClient(tt.mockHandler)
|
||||
client := conngh.NewClient(ghClient)
|
||||
|
||||
// Call the method being tested
|
||||
app, err := client.GetApp(context.Background())
|
||||
|
||||
// Check the error
|
||||
if tt.wantErr != nil {
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tt.wantApp, app)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantApp, app)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGithubClient_GetAppInstallation(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mockHandler *http.Client
|
||||
appToken string
|
||||
installationID string
|
||||
wantInstallation conngh.AppInstallation
|
||||
wantErr bool
|
||||
errContains string
|
||||
}{
|
||||
{
|
||||
name: "get disabled app installation successfully",
|
||||
mockHandler: mockhub.NewMockedHTTPClient(
|
||||
mockhub.WithRequestMatchHandler(
|
||||
mockhub.GetAppInstallationsByInstallationId,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
installation := &github.Installation{
|
||||
ID: github.Ptr(int64(67890)),
|
||||
SuspendedAt: github.Ptr(github.Timestamp{Time: time.Now()}),
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(installation))
|
||||
}),
|
||||
),
|
||||
),
|
||||
appToken: "test-app-token",
|
||||
installationID: "67890",
|
||||
wantInstallation: conngh.AppInstallation{
|
||||
ID: 67890,
|
||||
Enabled: false,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "get enabled app installation successfully",
|
||||
mockHandler: mockhub.NewMockedHTTPClient(
|
||||
mockhub.WithRequestMatchHandler(
|
||||
mockhub.GetAppInstallationsByInstallationId,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
installation := &github.Installation{
|
||||
ID: github.Ptr(int64(67890)),
|
||||
SuspendedAt: nil,
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(installation))
|
||||
}),
|
||||
),
|
||||
),
|
||||
appToken: "test-app-token",
|
||||
installationID: "67890",
|
||||
wantInstallation: conngh.AppInstallation{
|
||||
ID: 67890,
|
||||
Enabled: true,
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid installation ID",
|
||||
mockHandler: mockhub.NewMockedHTTPClient(),
|
||||
appToken: "test-app-token",
|
||||
installationID: "not-a-number",
|
||||
wantInstallation: conngh.AppInstallation{},
|
||||
wantErr: true,
|
||||
errContains: "invalid installation ID",
|
||||
},
|
||||
{
|
||||
name: "service unavailable",
|
||||
mockHandler: mockhub.NewMockedHTTPClient(
|
||||
mockhub.WithRequestMatchHandler(
|
||||
mockhub.GetAppInstallationsByInstallationId,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
||||
Response: &http.Response{
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
},
|
||||
Message: "Service unavailable",
|
||||
}))
|
||||
}),
|
||||
),
|
||||
),
|
||||
appToken: "test-app-token",
|
||||
installationID: "67890",
|
||||
wantInstallation: conngh.AppInstallation{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "installation not found",
|
||||
mockHandler: mockhub.NewMockedHTTPClient(
|
||||
mockhub.WithRequestMatchHandler(
|
||||
mockhub.GetAppInstallationsByInstallationId,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
||||
Response: &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
},
|
||||
Message: "Not Found",
|
||||
}))
|
||||
}),
|
||||
),
|
||||
),
|
||||
appToken: "test-app-token",
|
||||
installationID: "99999",
|
||||
wantInstallation: conngh.AppInstallation{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "other error",
|
||||
mockHandler: mockhub.NewMockedHTTPClient(
|
||||
mockhub.WithRequestMatchHandler(
|
||||
mockhub.GetAppInstallationsByInstallationId,
|
||||
http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{
|
||||
Response: &http.Response{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
},
|
||||
Message: "Internal server error",
|
||||
}))
|
||||
}),
|
||||
),
|
||||
),
|
||||
appToken: "test-app-token",
|
||||
installationID: "67890",
|
||||
wantInstallation: conngh.AppInstallation{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create a mock client
|
||||
ghClient := github.NewClient(tt.mockHandler)
|
||||
client := conngh.NewClient(ghClient)
|
||||
|
||||
// Call the method being tested
|
||||
installation, err := client.GetAppInstallation(context.Background(), tt.installationID)
|
||||
|
||||
// Check the error
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
if tt.errContains != "" {
|
||||
assert.Contains(t, err.Error(), tt.errContains)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// Check the result
|
||||
assert.Equal(t, tt.wantInstallation, installation)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/connection"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
//go:generate mockery --name GithubFactory --structname MockGithubFactory --inpackage --filename factory_mock.go --with-expecter
|
||||
type GithubFactory interface {
|
||||
New(ctx context.Context, ghToken common.RawSecureValue) Client
|
||||
}
|
||||
|
||||
type Connection struct {
|
||||
obj *provisioning.Connection
|
||||
ghFactory GithubFactory
|
||||
}
|
||||
|
||||
func NewConnection(
|
||||
obj *provisioning.Connection,
|
||||
factory GithubFactory,
|
||||
) Connection {
|
||||
return Connection{
|
||||
obj: obj,
|
||||
ghFactory: factory,
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
//TODO(ferruvich): these probably need to be setup in API configuration.
|
||||
githubInstallationURL = "https://github.com/settings/installations"
|
||||
jwtExpirationMinutes = 10 // GitHub Apps JWT tokens expire in 10 minutes maximum
|
||||
)
|
||||
|
||||
// Mutate performs in place mutation of the underneath resource.
|
||||
func (c *Connection) Mutate(_ context.Context) error {
|
||||
// Do nothing in case spec.Github is nil.
|
||||
// If this field is required, we should fail at validation time.
|
||||
if c.obj.Spec.GitHub == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.obj.Spec.URL = fmt.Sprintf("%s/%s", githubInstallationURL, c.obj.Spec.GitHub.InstallationID)
|
||||
|
||||
// Generate JWT token if private key is being provided.
|
||||
// Same as for the spec.Github, if such a field is required, Validation will take care of that.
|
||||
if !c.obj.Secure.PrivateKey.Create.IsZero() {
|
||||
token, err := generateToken(c.obj.Spec.GitHub.AppID, c.obj.Secure.PrivateKey.Create)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate JWT token: %w", err)
|
||||
}
|
||||
|
||||
// Store the generated token
|
||||
c.obj.Secure.Token = common.InlineSecureValue{Create: token}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Token generates and returns the Connection token.
|
||||
func generateToken(appID string, privateKey common.RawSecureValue) (common.RawSecureValue, error) {
|
||||
// Decode base64-encoded private key
|
||||
privateKeyPEM, err := base64.StdEncoding.DecodeString(string(privateKey))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode base64 private key: %w", err)
|
||||
}
|
||||
|
||||
// Parse the private key
|
||||
key, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyPEM)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to parse private key: %w", err)
|
||||
}
|
||||
|
||||
// Create the JWT token
|
||||
now := time.Now()
|
||||
claims := jwt.RegisteredClaims{
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(jwtExpirationMinutes) * time.Minute)),
|
||||
Issuer: appID,
|
||||
}
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
|
||||
signedToken, err := token.SignedString(key)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sign JWT token: %w", err)
|
||||
}
|
||||
|
||||
return common.RawSecureValue(signedToken), nil
|
||||
}
|
||||
|
||||
// Validate ensures the resource _looks_ correct.
|
||||
func (c *Connection) Validate(ctx context.Context) error {
|
||||
list := field.ErrorList{}
|
||||
|
||||
if c.obj.Spec.Type != provisioning.GithubConnectionType {
|
||||
list = append(list, field.Invalid(field.NewPath("spec", "type"), c.obj.Spec.Type, "invalid connection type"))
|
||||
|
||||
// Doesn't make much sense to continue validating a connection which is not a Github one.
|
||||
return toError(c.obj.GetName(), list)
|
||||
}
|
||||
|
||||
if c.obj.Spec.GitHub == nil {
|
||||
list = append(
|
||||
list, field.Required(field.NewPath("spec", "github"), "github info must be specified for GitHub connection"),
|
||||
)
|
||||
|
||||
// Doesn't make much sense to continue validating a connection with no information.
|
||||
return toError(c.obj.GetName(), list)
|
||||
}
|
||||
|
||||
if c.obj.Secure.PrivateKey.IsZero() {
|
||||
list = append(list, field.Required(field.NewPath("secure", "privateKey"), "privateKey must be specified for GitHub connection"))
|
||||
}
|
||||
if c.obj.Secure.Token.IsZero() {
|
||||
list = append(list, field.Required(field.NewPath("secure", "token"), "token must be specified for GitHub connection"))
|
||||
}
|
||||
if !c.obj.Secure.ClientSecret.IsZero() {
|
||||
list = append(list, field.Forbidden(field.NewPath("secure", "clientSecret"), "clientSecret is forbidden in GitHub connection"))
|
||||
}
|
||||
|
||||
// Validate GitHub configuration fields
|
||||
if c.obj.Spec.GitHub.AppID == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github", "appID"), "appID must be specified for GitHub connection"))
|
||||
}
|
||||
if c.obj.Spec.GitHub.InstallationID == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "github", "installationID"), "installationID must be specified for GitHub connection"))
|
||||
}
|
||||
|
||||
// In case we have any error above, we don't go forward with the validation, and return the errors.
|
||||
if len(list) > 0 {
|
||||
return toError(c.obj.GetName(), list)
|
||||
}
|
||||
|
||||
// Validating app content via GH API
|
||||
if err := c.validateAppAndInstallation(ctx); err != nil {
|
||||
list = append(list, err)
|
||||
}
|
||||
|
||||
return toError(c.obj.GetName(), list)
|
||||
}
|
||||
|
||||
// validateAppAndInstallation validates the appID and installationID against the given github token.
|
||||
func (c *Connection) validateAppAndInstallation(ctx context.Context) *field.Error {
|
||||
ghClient := c.ghFactory.New(ctx, c.obj.Secure.Token.Create)
|
||||
|
||||
app, err := ghClient.GetApp(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrServiceUnavailable) {
|
||||
return field.InternalError(field.NewPath("spec", "token"), ErrServiceUnavailable)
|
||||
}
|
||||
return field.Invalid(field.NewPath("spec", "token"), "[REDACTED]", "invalid token")
|
||||
}
|
||||
|
||||
if fmt.Sprintf("%d", app.ID) != c.obj.Spec.GitHub.AppID {
|
||||
return field.Invalid(field.NewPath("spec", "appID"), c.obj.Spec.GitHub.AppID, "appID mismatch")
|
||||
}
|
||||
|
||||
_, err = ghClient.GetAppInstallation(ctx, c.obj.Spec.GitHub.InstallationID)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrServiceUnavailable) {
|
||||
return field.InternalError(field.NewPath("spec", "token"), ErrServiceUnavailable)
|
||||
}
|
||||
return field.Invalid(field.NewPath("spec", "installationID"), c.obj.Spec.GitHub.InstallationID, "invalid installation ID")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// toError converts a field.ErrorList to an error, returning nil if the list is empty
|
||||
func toError(name string, list field.ErrorList) error {
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
return apierrors.NewInvalid(
|
||||
provisioning.ConnectionResourceInfo.GroupVersionKind().GroupKind(),
|
||||
name,
|
||||
list,
|
||||
)
|
||||
}
|
||||
|
||||
var (
|
||||
_ connection.Connection = (*Connection)(nil)
|
||||
)
|
||||
@@ -0,0 +1,434 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
//nolint:gosec // Test RSA private key (generated for testing purposes only)
|
||||
const testPrivateKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEowIBAAKCAQEAoInVbLY9io2Q/wHvUIXlEHg2Qyvd8eRzBAVEJ92DS6fx9H10
|
||||
06V0VRm78S0MXyo6i+n8ZAbZ0/R+GWpP2Ephxm0Gs2zo+iO2mpB19xQFI4o6ZTOw
|
||||
b2WyjSaa2Vr4oyDkqti6AvfjW4VUAu932e08GkgwmmQSHXj7FX2CMWjgUwTTcuaX
|
||||
65SHNKLNYLUP0HTumLzoZeqDTdoMMpKNdgH9Avr4/8vkVJ0mD6rqvxnw3JHsseNO
|
||||
WdQTxf2aApBNHIIKxWZ2i/ZmjLNey7kltgjEquGiBdJvip3fHhH5XHdkrXcjRtnw
|
||||
OJDnDmi5lQwv5yUBOSkbvbXRv/L/m0YLoD/fbwIDAQABAoIBAFfl//hM8/cnuesV
|
||||
+R1Con/ZAgTXQOdPqPXbmEyniVrkMqMmCdBUOBTcST4s5yg36+RtkeaGpb/ajyyF
|
||||
PAB2AYDucwvMpudGpJWOYTiOOp4R8hU1LvZfXVrRd1lo6NgQi4NLtNUpOtACeVQ+
|
||||
H4Yv0YemXQ47mnuOoRNMK/u3q5NoIdSahWptXBgUno8KklNpUrH3IYWaUxfBzDN3
|
||||
2xsVRTn2SfTSyoDmTDdTgptJONmoK1/sV7UsgWksdFc6XyYhsFAZgOGEJrBABRvF
|
||||
546dyQ0cWxuPyVXpM7CN3tqC5ssvLjElg3LicK1V6gnjpdRnnvX88d1Eh3Uc/9IM
|
||||
OZInT2ECgYEA6W8sQXTWinyEwl8SDKKMbB2ApIghAcFgdRxprZE4WFxjsYNCNL70
|
||||
dnSB7MRuzmxf5W77cV0N7JhH66N8HvY6Xq9olrpQ5dNttR4w8Pyv3wavDe8x7seL
|
||||
5L2Xtbu7ihDr8Dk27MjiBSin3IxhBP5CJS910+pR6LrAWtEuU+FzFfECgYEAsA6y
|
||||
qxHhCMXlTnauXhsnmPd1g61q7chW8kLQFYtHMLlQlgjHTW7irDZ9cPbPYDNjwRLO
|
||||
7KLorcpv2NKe7rqq2ZyCm6hf1b9WnlQjo3dLpNWMu6fhy/smK8MgbRqcWpX+oTKF
|
||||
79mK6hbY7o6eBzsQHBl7Z+LBNuwYmp9qOodPa18CgYEArv6ipKdcNhFGzRfMRiCN
|
||||
OHederp6VACNuP2F05IsNUF9kxOdTEFirnKE++P+VU01TqA2azOhPp6iO+ohIGzi
|
||||
MR06QNSH1OL9OWvasK4dggpWrRGF00VQgDgJRTnpS4WH+lxJ6pRlrAxgWpv6F24s
|
||||
VAgSQr1Ejj2B+hMasdMvHWECgYBJ4uE4yhgXBnZlp4kmFV9Y4wF+cZkekaVrpn6N
|
||||
jBYkbKFVVfnOlWqru3KJpgsB5I9IyAvvY68iwIKQDFSG+/AXw4dMrC0MF3DSoZ0T
|
||||
TU2Br92QI7SvVod+djV1lGVp3ukt3XY4YqPZ+hywgUnw3uiz4j3YK2HLGup4ec6r
|
||||
IX5DIQKBgHRLzvT3zqtlR1Oh0vv098clLwt+pGzXOxzJpxioOa5UqK13xIpFXbcg
|
||||
iWUVh5YXCcuqaICUv4RLIEac5xQitk9Is/9IhP0NJ/81rHniosvdSpCeFXzxTImS
|
||||
B8Uc0WUgheB4+yVKGnYpYaSOgFFI5+1BYUva/wDHLy2pWHz39Usb
|
||||
-----END RSA PRIVATE KEY-----`
|
||||
|
||||
func TestConnection_Mutate(t *testing.T) {
|
||||
t.Run("should add URL to Github connection", func(t *testing.T) {
|
||||
c := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Name: "test-private-key",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockFactory := NewMockGithubFactory(t)
|
||||
conn := NewConnection(c, mockFactory)
|
||||
|
||||
require.NoError(t, conn.Mutate(context.Background()))
|
||||
assert.Equal(t, "https://github.com/settings/installations/456", c.Spec.URL)
|
||||
})
|
||||
|
||||
t.Run("should generate JWT token when private key is provided", func(t *testing.T) {
|
||||
privateKeyBase64 := base64.StdEncoding.EncodeToString([]byte(testPrivateKeyPEM))
|
||||
|
||||
c := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue(privateKeyBase64),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockFactory := NewMockGithubFactory(t)
|
||||
conn := NewConnection(c, mockFactory)
|
||||
|
||||
require.NoError(t, conn.Mutate(context.Background()))
|
||||
assert.Equal(t, "https://github.com/settings/installations/456", c.Spec.URL)
|
||||
assert.False(t, c.Secure.Token.Create.IsZero(), "JWT token should be generated")
|
||||
})
|
||||
|
||||
t.Run("should do nothing when GitHub config is nil", func(t *testing.T) {
|
||||
c := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GitlabConnectionType,
|
||||
Gitlab: &provisioning.GitlabConnectionConfig{
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockFactory := NewMockGithubFactory(t)
|
||||
conn := NewConnection(c, mockFactory)
|
||||
|
||||
require.NoError(t, conn.Mutate(context.Background()))
|
||||
})
|
||||
|
||||
t.Run("should fail when private key is not base64", func(t *testing.T) {
|
||||
c := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("invalid-key"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockFactory := NewMockGithubFactory(t)
|
||||
conn := NewConnection(c, mockFactory)
|
||||
|
||||
err := conn.Mutate(context.Background())
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to generate JWT token")
|
||||
assert.Contains(t, err.Error(), "failed to decode base64 private key")
|
||||
})
|
||||
|
||||
t.Run("should fail when private key is invalid", func(t *testing.T) {
|
||||
c := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue(base64.StdEncoding.EncodeToString([]byte("invalid-key"))),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockFactory := NewMockGithubFactory(t)
|
||||
conn := NewConnection(c, mockFactory)
|
||||
|
||||
err := conn.Mutate(context.Background())
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "failed to generate JWT token")
|
||||
assert.Contains(t, err.Error(), "failed to parse private key")
|
||||
})
|
||||
}
|
||||
|
||||
func TestConnection_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
connection *provisioning.Connection
|
||||
setupMock func(*MockGithubFactory)
|
||||
wantErr bool
|
||||
errMsgContains []string
|
||||
}{
|
||||
{
|
||||
name: "invalid type returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: "invalid",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsgContains: []string{"spec.type"},
|
||||
},
|
||||
{
|
||||
name: "github type without github config returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsgContains: []string{"spec.github"},
|
||||
},
|
||||
{
|
||||
name: "github type without private key returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsgContains: []string{"secure.privateKey"},
|
||||
},
|
||||
{
|
||||
name: "github type without token returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-private-key"),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsgContains: []string{"secure.token"},
|
||||
},
|
||||
{
|
||||
name: "github type with client secret returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
ClientSecret: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-client-secret"),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsgContains: []string{"secure.clientSecret"},
|
||||
},
|
||||
{
|
||||
name: "github type without appID returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-private-key"),
|
||||
},
|
||||
Token: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-token"),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsgContains: []string{"spec.github.appID"},
|
||||
},
|
||||
{
|
||||
name: "github type without installationID returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Name: "test-private-key",
|
||||
},
|
||||
Token: common.InlineSecureValue{
|
||||
Name: "test-token",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsgContains: []string{"spec.github.installationID"},
|
||||
},
|
||||
{
|
||||
name: "github type with valid config is valid",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-private-key"),
|
||||
},
|
||||
Token: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-token"),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
setupMock: func(mockFactory *MockGithubFactory) {
|
||||
mockClient := NewMockClient(t)
|
||||
|
||||
mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
|
||||
mockClient.EXPECT().GetApp(mock.Anything).Return(App{ID: 123, Slug: "test-app"}, nil)
|
||||
mockClient.EXPECT().GetAppInstallation(mock.Anything, "456").Return(AppInstallation{ID: 456}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "problem getting app returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-private-key"),
|
||||
},
|
||||
Token: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-token"),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsgContains: []string{"spec.token", "[REDACTED]"},
|
||||
setupMock: func(mockFactory *MockGithubFactory) {
|
||||
mockClient := NewMockClient(t)
|
||||
|
||||
mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
|
||||
mockClient.EXPECT().GetApp(mock.Anything).Return(App{}, assert.AnError)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "mismatched app ID returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-private-key"),
|
||||
},
|
||||
Token: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-token"),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsgContains: []string{"spec.appID"},
|
||||
setupMock: func(mockFactory *MockGithubFactory) {
|
||||
mockClient := NewMockClient(t)
|
||||
|
||||
mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
|
||||
mockClient.EXPECT().GetApp(mock.Anything).Return(App{ID: 444, Slug: "test-app"}, nil)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "problem when getting installation returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-private-key"),
|
||||
},
|
||||
Token: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-token"),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsgContains: []string{"spec.installationID", "456"},
|
||||
setupMock: func(mockFactory *MockGithubFactory) {
|
||||
mockClient := NewMockClient(t)
|
||||
|
||||
mockFactory.EXPECT().New(mock.Anything, common.RawSecureValue("test-token")).Return(mockClient)
|
||||
mockClient.EXPECT().GetApp(mock.Anything).Return(App{ID: 123, Slug: "test-app"}, nil)
|
||||
mockClient.EXPECT().GetAppInstallation(mock.Anything, "456").Return(AppInstallation{}, assert.AnError)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockFactory := NewMockGithubFactory(t)
|
||||
if tt.setupMock != nil {
|
||||
tt.setupMock(mockFactory)
|
||||
}
|
||||
|
||||
conn := NewConnection(tt.connection, mockFactory)
|
||||
err := conn.Validate(context.Background())
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
for _, msg := range tt.errMsgContains {
|
||||
assert.Contains(t, err.Error(), msg)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/connection"
|
||||
)
|
||||
|
||||
type extra struct {
|
||||
factory GithubFactory
|
||||
}
|
||||
|
||||
func (e *extra) Type() provisioning.ConnectionType {
|
||||
return provisioning.GithubConnectionType
|
||||
}
|
||||
|
||||
func (e *extra) Build(ctx context.Context, connection *provisioning.Connection) (connection.Connection, error) {
|
||||
logger := logging.FromContext(ctx)
|
||||
if connection == nil || connection.Spec.GitHub == nil {
|
||||
logger.Error("connection is nil or github info is nil")
|
||||
|
||||
return nil, fmt.Errorf("invalid github connection")
|
||||
}
|
||||
|
||||
c := NewConnection(connection, e.factory)
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func Extra(factory GithubFactory) connection.Extra {
|
||||
return &extra{
|
||||
factory: factory,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package github_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/connection/github"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestExtra_Type(t *testing.T) {
|
||||
t.Run("should return GithubConnectionType", func(t *testing.T) {
|
||||
mockFactory := github.NewMockGithubFactory(t)
|
||||
e := github.Extra(mockFactory)
|
||||
result := e.Type()
|
||||
assert.Equal(t, provisioning.GithubConnectionType, result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestExtra_Build(t *testing.T) {
|
||||
t.Run("should successfully build connection", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("test-private-key"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockFactory := github.NewMockGithubFactory(t)
|
||||
|
||||
e := github.Extra(mockFactory)
|
||||
|
||||
result, err := e.Build(ctx, conn)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
})
|
||||
|
||||
t.Run("should handle different connection configurations", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "another-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "789",
|
||||
InstallationID: "101112",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Name: "existing-private-key",
|
||||
},
|
||||
Token: common.InlineSecureValue{
|
||||
Name: "existing-token",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockFactory := github.NewMockGithubFactory(t)
|
||||
|
||||
e := github.Extra(mockFactory)
|
||||
|
||||
result, err := e.Build(ctx, conn)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
})
|
||||
|
||||
t.Run("should build connection with background context", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockFactory := github.NewMockGithubFactory(t)
|
||||
e := github.Extra(mockFactory)
|
||||
result, err := e.Build(ctx, conn)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
})
|
||||
|
||||
t.Run("should always pass empty token to factory.New", func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
conn := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
Token: common.InlineSecureValue{
|
||||
Create: common.NewSecretValue("some-token"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mockFactory := github.NewMockGithubFactory(t)
|
||||
e := github.Extra(mockFactory)
|
||||
result, err := e.Build(ctx, conn)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package github
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/go-github/v70/github"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
)
|
||||
|
||||
// Factory creates new GitHub clients.
|
||||
// It exists only for the ability to test the code easily.
|
||||
type Factory struct {
|
||||
// Client allows overriding the client to use in the GH client returned. It exists primarily for testing.
|
||||
// FIXME: we should replace in this way. We should add some options pattern for the factory.
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
func ProvideFactory() GithubFactory {
|
||||
return &Factory{}
|
||||
}
|
||||
|
||||
func (r *Factory) New(ctx context.Context, ghToken common.RawSecureValue) Client {
|
||||
if r.Client != nil {
|
||||
return NewClient(github.NewClient(r.Client))
|
||||
}
|
||||
|
||||
if !ghToken.IsZero() {
|
||||
tokenSrc := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: string(ghToken)},
|
||||
)
|
||||
tokenClient := oauth2.NewClient(ctx, tokenSrc)
|
||||
return NewClient(github.NewClient(tokenClient))
|
||||
}
|
||||
|
||||
return NewClient(github.NewClient(&http.Client{}))
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
// Code generated by mockery v2.53.4. DO NOT EDIT.
|
||||
|
||||
package github
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// MockGithubFactory is an autogenerated mock type for the GithubFactory type
|
||||
type MockGithubFactory struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
type MockGithubFactory_Expecter struct {
|
||||
mock *mock.Mock
|
||||
}
|
||||
|
||||
func (_m *MockGithubFactory) EXPECT() *MockGithubFactory_Expecter {
|
||||
return &MockGithubFactory_Expecter{mock: &_m.Mock}
|
||||
}
|
||||
|
||||
// New provides a mock function with given fields: ctx, ghToken
|
||||
func (_m *MockGithubFactory) New(ctx context.Context, ghToken v0alpha1.RawSecureValue) Client {
|
||||
ret := _m.Called(ctx, ghToken)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for New")
|
||||
}
|
||||
|
||||
var r0 Client
|
||||
if rf, ok := ret.Get(0).(func(context.Context, v0alpha1.RawSecureValue) Client); ok {
|
||||
r0 = rf(ctx, ghToken)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(Client)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockGithubFactory_New_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'New'
|
||||
type MockGithubFactory_New_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// New is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - ghToken v0alpha1.RawSecureValue
|
||||
func (_e *MockGithubFactory_Expecter) New(ctx interface{}, ghToken interface{}) *MockGithubFactory_New_Call {
|
||||
return &MockGithubFactory_New_Call{Call: _e.mock.On("New", ctx, ghToken)}
|
||||
}
|
||||
|
||||
func (_c *MockGithubFactory_New_Call) Run(run func(ctx context.Context, ghToken v0alpha1.RawSecureValue)) *MockGithubFactory_New_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(v0alpha1.RawSecureValue))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubFactory_New_Call) Return(_a0 Client) *MockGithubFactory_New_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockGithubFactory_New_Call) RunAndReturn(run func(context.Context, v0alpha1.RawSecureValue) Client) *MockGithubFactory_New_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewMockGithubFactory creates a new instance of MockGithubFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewMockGithubFactory(t interface {
|
||||
mock.TestingT
|
||||
Cleanup(func())
|
||||
}) *MockGithubFactory {
|
||||
mock := &MockGithubFactory{}
|
||||
mock.Mock.Test(t)
|
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) })
|
||||
|
||||
return mock
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package connection
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
githubInstallationURL = "https://github.com/settings/installations"
|
||||
)
|
||||
|
||||
func MutateConnection(connection *provisioning.Connection) error {
|
||||
switch connection.Spec.Type {
|
||||
case provisioning.GithubConnectionType:
|
||||
// Do nothing in case spec.Github is nil.
|
||||
// If this field is required, we should fail at validation time.
|
||||
if connection.Spec.GitHub == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
connection.Spec.URL = fmt.Sprintf("%s/%s", githubInstallationURL, connection.Spec.GitHub.InstallationID)
|
||||
return nil
|
||||
default:
|
||||
// TODO: we need to setup the URL for bitbucket and gitlab.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package connection_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/connection"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestMutateConnection(t *testing.T) {
|
||||
t.Run("should add URL to Github connection", func(t *testing.T) {
|
||||
c := &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Name: "test-private-key",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
require.NoError(t, connection.MutateConnection(c))
|
||||
assert.Equal(t, "https://github.com/settings/installations/456", c.Spec.URL)
|
||||
})
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
package connection
|
||||
|
||||
import (
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
)
|
||||
|
||||
func ValidateConnection(connection *provisioning.Connection) error {
|
||||
list := field.ErrorList{}
|
||||
|
||||
if connection.Spec.Type == "" {
|
||||
list = append(list, field.Required(field.NewPath("spec", "type"), "type must be specified"))
|
||||
}
|
||||
|
||||
switch connection.Spec.Type {
|
||||
case provisioning.GithubConnectionType:
|
||||
list = append(list, validateGithubConnection(connection)...)
|
||||
case provisioning.BitbucketConnectionType:
|
||||
list = append(list, validateBitbucketConnection(connection)...)
|
||||
case provisioning.GitlabConnectionType:
|
||||
list = append(list, validateGitlabConnection(connection)...)
|
||||
default:
|
||||
list = append(
|
||||
list, field.NotSupported(
|
||||
field.NewPath("spec", "type"),
|
||||
connection.Spec.Type,
|
||||
[]provisioning.ConnectionType{
|
||||
provisioning.GithubConnectionType,
|
||||
provisioning.BitbucketConnectionType,
|
||||
provisioning.GitlabConnectionType,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return toError(connection.GetName(), list)
|
||||
}
|
||||
|
||||
func validateGithubConnection(connection *provisioning.Connection) field.ErrorList {
|
||||
list := field.ErrorList{}
|
||||
|
||||
if connection.Spec.GitHub == nil {
|
||||
list = append(
|
||||
list, field.Required(field.NewPath("spec", "github"), "github info must be specified for GitHub connection"),
|
||||
)
|
||||
}
|
||||
|
||||
if connection.Secure.PrivateKey.IsZero() {
|
||||
list = append(list, field.Required(field.NewPath("secure", "privateKey"), "privateKey must be specified for GitHub connection"))
|
||||
}
|
||||
if !connection.Secure.ClientSecret.IsZero() {
|
||||
list = append(list, field.Forbidden(field.NewPath("secure", "clientSecret"), "clientSecret is forbidden in GitHub connection"))
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func validateBitbucketConnection(connection *provisioning.Connection) field.ErrorList {
|
||||
list := field.ErrorList{}
|
||||
|
||||
if connection.Spec.Bitbucket == nil {
|
||||
list = append(
|
||||
list, field.Required(field.NewPath("spec", "bitbucket"), "bitbucket info must be specified in Bitbucket connection"),
|
||||
)
|
||||
}
|
||||
if connection.Secure.ClientSecret.IsZero() {
|
||||
list = append(list, field.Required(field.NewPath("secure", "clientSecret"), "clientSecret must be specified for Bitbucket connection"))
|
||||
}
|
||||
if !connection.Secure.PrivateKey.IsZero() {
|
||||
list = append(list, field.Forbidden(field.NewPath("secure", "privateKey"), "privateKey is forbidden in Bitbucket connection"))
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
func validateGitlabConnection(connection *provisioning.Connection) field.ErrorList {
|
||||
list := field.ErrorList{}
|
||||
|
||||
if connection.Spec.Gitlab == nil {
|
||||
list = append(
|
||||
list, field.Required(field.NewPath("spec", "gitlab"), "gitlab info must be specified in Gitlab connection"),
|
||||
)
|
||||
}
|
||||
if connection.Secure.ClientSecret.IsZero() {
|
||||
list = append(list, field.Required(field.NewPath("secure", "clientSecret"), "clientSecret must be specified for Gitlab connection"))
|
||||
}
|
||||
if !connection.Secure.PrivateKey.IsZero() {
|
||||
list = append(list, field.Forbidden(field.NewPath("secure", "privateKey"), "privateKey is forbidden in Gitlab connection"))
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
// toError converts a field.ErrorList to an error, returning nil if the list is empty
|
||||
func toError(name string, list field.ErrorList) error {
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
return apierrors.NewInvalid(
|
||||
provisioning.ConnectionResourceInfo.GroupVersionKind().GroupKind(),
|
||||
name,
|
||||
list,
|
||||
)
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
package connection_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/provisioning/pkg/connection"
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestValidateConnection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
connection *provisioning.Connection
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "empty type returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "spec.type",
|
||||
},
|
||||
{
|
||||
name: "invalid type returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: "invalid",
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "spec.type",
|
||||
},
|
||||
{
|
||||
name: "github type without github config returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "spec.github",
|
||||
},
|
||||
{
|
||||
name: "github type without private key returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "secure.privateKey",
|
||||
},
|
||||
{
|
||||
name: "github type with client secret returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Name: "test-private-key",
|
||||
},
|
||||
ClientSecret: common.InlineSecureValue{
|
||||
Name: "test-client-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "secure.clientSecret",
|
||||
},
|
||||
{
|
||||
name: "github type with github config is valid",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GithubConnectionType,
|
||||
GitHub: &provisioning.GitHubConnectionConfig{
|
||||
AppID: "123",
|
||||
InstallationID: "456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Name: "test-private-key",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "bitbucket type without bitbucket config returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.BitbucketConnectionType,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "spec.bitbucket",
|
||||
},
|
||||
{
|
||||
name: "bitbucket type without client secret returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.BitbucketConnectionType,
|
||||
Bitbucket: &provisioning.BitbucketConnectionConfig{
|
||||
ClientID: "client-123",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "secure.clientSecret",
|
||||
},
|
||||
{
|
||||
name: "bitbucket type with private key returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.BitbucketConnectionType,
|
||||
Bitbucket: &provisioning.BitbucketConnectionConfig{
|
||||
ClientID: "client-123",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Name: "test-private-key",
|
||||
},
|
||||
ClientSecret: common.InlineSecureValue{
|
||||
Name: "test-client-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "secure.privateKey",
|
||||
},
|
||||
{
|
||||
name: "bitbucket type with bitbucket config is valid",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.BitbucketConnectionType,
|
||||
Bitbucket: &provisioning.BitbucketConnectionConfig{
|
||||
ClientID: "client-123",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
ClientSecret: common.InlineSecureValue{
|
||||
Name: "test-client-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "gitlab type without gitlab config returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GitlabConnectionType,
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "spec.gitlab",
|
||||
},
|
||||
{
|
||||
name: "gitlab type without client secret returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GitlabConnectionType,
|
||||
Gitlab: &provisioning.GitlabConnectionConfig{
|
||||
ClientID: "client-456",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "secure.clientSecret",
|
||||
},
|
||||
{
|
||||
name: "gitlab type with private key returns error",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GitlabConnectionType,
|
||||
Gitlab: &provisioning.GitlabConnectionConfig{
|
||||
ClientID: "client-456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
PrivateKey: common.InlineSecureValue{
|
||||
Name: "test-private-key",
|
||||
},
|
||||
ClientSecret: common.InlineSecureValue{
|
||||
Name: "test-client-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "secure.privateKey",
|
||||
},
|
||||
{
|
||||
name: "gitlab type with gitlab config is valid",
|
||||
connection: &provisioning.Connection{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-connection"},
|
||||
Spec: provisioning.ConnectionSpec{
|
||||
Type: provisioning.GitlabConnectionType,
|
||||
Gitlab: &provisioning.GitlabConnectionConfig{
|
||||
ClientID: "client-456",
|
||||
},
|
||||
},
|
||||
Secure: provisioning.ConnectionSecure{
|
||||
ClientSecret: common.InlineSecureValue{
|
||||
Name: "test-client-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := connection.ValidateConnection(tt.connection)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
if tt.errMsg != "" {
|
||||
assert.Contains(t, err.Error(), tt.errMsg)
|
||||
}
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
||||
type ConnectionSecureApplyConfiguration struct {
|
||||
PrivateKey *commonv0alpha1.InlineSecureValue `json:"privateKey,omitempty"`
|
||||
ClientSecret *commonv0alpha1.InlineSecureValue `json:"clientSecret,omitempty"`
|
||||
Token *commonv0alpha1.InlineSecureValue `json:"webhook,omitempty"`
|
||||
Token *commonv0alpha1.InlineSecureValue `json:"token,omitempty"`
|
||||
}
|
||||
|
||||
// ConnectionSecureApplyConfiguration constructs a declarative configuration of the ConnectionSecure type for use with
|
||||
|
||||
+14
-1
@@ -336,7 +336,7 @@ rudderstack_data_plane_url =
|
||||
rudderstack_sdk_url =
|
||||
|
||||
# Rudderstack v3 SDK, optional, defaults to false. If set, Rudderstack v3 SDK will be used instead of v1
|
||||
rudderstack_v3_sdk_url =
|
||||
rudderstack_v3_sdk_url =
|
||||
|
||||
# Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config
|
||||
rudderstack_config_url =
|
||||
@@ -2079,8 +2079,14 @@ enable =
|
||||
# To enable features by default, set `Expression: "true"` in:
|
||||
# https://github.com/grafana/grafana/blob/main/pkg/services/featuremgmt/registry.go
|
||||
|
||||
# The feature_toggles section supports feature flags of a number of types,
|
||||
# including boolean, string, integer, float, and structured values, following the OpenFeature specification.
|
||||
#
|
||||
# feature1 = true
|
||||
# feature2 = false
|
||||
# feature3 = "foobar"
|
||||
# feature4 = 1.5
|
||||
# feature5 = { "foo": "bar" }
|
||||
|
||||
[feature_toggles.openfeature]
|
||||
# This is EXPERIMENTAL. Please, do not use this section
|
||||
@@ -2281,3 +2287,10 @@ allow_image_rendering = true
|
||||
# will check if there has been any changes to the repository not propagated by a webhook.
|
||||
# The minimum value is 10 seconds.
|
||||
min_sync_interval = 10s
|
||||
|
||||
#################################### Unified Storage ####################################
|
||||
[unified_storage]
|
||||
# index_path is the path where unified storage can store its index files for search.
|
||||
# If empty, defaults to "<data_dir>/unified-search/bleve" (see [paths] section).
|
||||
# Please note that sharing the same index_path between multiple running Grafana instances is not supported.
|
||||
index_path =
|
||||
|
||||
+8
-3
@@ -323,7 +323,7 @@
|
||||
;rudderstack_sdk_url =
|
||||
|
||||
# Rudderstack v3 SDK, optional, defaults to false. If set, Rudderstack v3 SDK will be used instead of v1
|
||||
;rudderstack_v3_sdk_url =
|
||||
;rudderstack_v3_sdk_url =
|
||||
|
||||
# Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config
|
||||
;rudderstack_config_url =
|
||||
@@ -1913,7 +1913,7 @@ default_datasource_uid =
|
||||
|
||||
# client_queue_max_size is the maximum size in bytes of the client queue
|
||||
# for Live connections. Defaults to 4MB.
|
||||
;client_queue_max_size =
|
||||
;client_queue_max_size =
|
||||
|
||||
#################################### Grafana Image Renderer Plugin ##########################
|
||||
[plugin.grafana-image-renderer]
|
||||
@@ -1996,9 +1996,14 @@ default_datasource_uid =
|
||||
|
||||
;enable = feature1,feature2
|
||||
|
||||
# The feature_toggles section supports feature flags of a number of types,
|
||||
# including boolean, string, integer, float, and structured values, following the OpenFeature specification.
|
||||
|
||||
;feature1 = true
|
||||
;feature2 = false
|
||||
|
||||
;feature3 = "foobar"
|
||||
;feature4 = 1.5
|
||||
;feature5 = { "foo": "bar" }
|
||||
[date_formats]
|
||||
# For information on what formatting patterns that are supported https://momentjs.com/docs/#/displaying/
|
||||
|
||||
|
||||
@@ -248,7 +248,7 @@
|
||||
"legend": {
|
||||
"values": ["percent"],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom"
|
||||
"placement": "right"
|
||||
},
|
||||
"pieType": "pie",
|
||||
"reduceOptions": {
|
||||
@@ -256,7 +256,7 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showLegend": false,
|
||||
"showLegend": true,
|
||||
"strokeWidth": 1,
|
||||
"text": {}
|
||||
},
|
||||
@@ -272,6 +272,15 @@
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Percent",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "renameByRegex",
|
||||
"options": {
|
||||
"regex": "^Backend-(.*)$",
|
||||
"renamePattern": "b-$1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "piechart"
|
||||
},
|
||||
{
|
||||
@@ -311,7 +320,7 @@
|
||||
"legend": {
|
||||
"values": ["value"],
|
||||
"displayMode": "table",
|
||||
"placement": "bottom"
|
||||
"placement": "right"
|
||||
},
|
||||
"pieType": "pie",
|
||||
"reduceOptions": {
|
||||
@@ -319,7 +328,7 @@
|
||||
"fields": "",
|
||||
"values": false
|
||||
},
|
||||
"showLegend": false,
|
||||
"showLegend": true,
|
||||
"strokeWidth": 1,
|
||||
"text": {}
|
||||
},
|
||||
@@ -335,6 +344,15 @@
|
||||
"timeFrom": null,
|
||||
"timeShift": null,
|
||||
"title": "Value",
|
||||
"transformations": [
|
||||
{
|
||||
"id": "renameByRegex",
|
||||
"options": {
|
||||
"regex": "(.*)",
|
||||
"renamePattern": "$1-how-much-wood-could-a-woodchuck-chuck-if-a-woodchuck-could-chuck-wood"
|
||||
}
|
||||
}
|
||||
],
|
||||
"type": "piechart"
|
||||
},
|
||||
{
|
||||
|
||||
@@ -48,6 +48,23 @@ scopes:
|
||||
operator: equals
|
||||
value: kids
|
||||
|
||||
# This scope appears in multiple places in the tree.
|
||||
# The defaultPath determines which path is shown when this scope is selected
|
||||
# (e.g., from a URL or programmatically), even if another path also links to it.
|
||||
shared-service:
|
||||
title: Shared Service
|
||||
# Path from the root node down to the direct scopeNode.
|
||||
# Node names are hierarchical (parent-child), so use the full names.
|
||||
# This points to: gdev-scopes > production > shared-service-prod
|
||||
defaultPath:
|
||||
- gdev-scopes
|
||||
- gdev-scopes-production
|
||||
- gdev-scopes-production-shared-service-prod
|
||||
filters:
|
||||
- key: service
|
||||
operator: equals
|
||||
value: shared
|
||||
|
||||
tree:
|
||||
gdev-scopes:
|
||||
title: gdev-scopes
|
||||
@@ -68,6 +85,13 @@ tree:
|
||||
nodeType: leaf
|
||||
linkId: app2
|
||||
linkType: scope
|
||||
# This node links to 'shared-service' scope.
|
||||
# The scope's defaultPath points here (production > gdev-scopes).
|
||||
shared-service-prod:
|
||||
title: Shared Service
|
||||
nodeType: leaf
|
||||
linkId: shared-service
|
||||
linkType: scope
|
||||
test-cases:
|
||||
title: Test cases
|
||||
nodeType: container
|
||||
@@ -83,6 +107,15 @@ tree:
|
||||
nodeType: leaf
|
||||
linkId: test-case-2
|
||||
linkType: scope
|
||||
# This node also links to the same 'shared-service' scope.
|
||||
# However, the scope's defaultPath points to the production path,
|
||||
# so selecting this scope will expand the tree to production > shared-service-prod.
|
||||
shared-service-test:
|
||||
title: Shared Service (also in Production)
|
||||
subTitle: defaultPath points to Production
|
||||
nodeType: leaf
|
||||
linkId: shared-service
|
||||
linkType: scope
|
||||
test-case-redirect:
|
||||
title: Test case with redirect
|
||||
nodeType: leaf
|
||||
|
||||
+16
-4
@@ -51,8 +51,9 @@ type Config struct {
|
||||
|
||||
// ScopeConfig is used for YAML parsing - converts to v0alpha1.ScopeSpec
|
||||
type ScopeConfig struct {
|
||||
Title string `yaml:"title"`
|
||||
Filters []ScopeFilterConfig `yaml:"filters"`
|
||||
Title string `yaml:"title"`
|
||||
DefaultPath []string `yaml:"defaultPath,omitempty"`
|
||||
Filters []ScopeFilterConfig `yaml:"filters"`
|
||||
}
|
||||
|
||||
// ScopeFilterConfig is used for YAML parsing - converts to v0alpha1.ScopeFilter
|
||||
@@ -116,9 +117,20 @@ func convertScopeSpec(cfg ScopeConfig) v0alpha1.ScopeSpec {
|
||||
for i, f := range cfg.Filters {
|
||||
filters[i] = convertFilter(f)
|
||||
}
|
||||
|
||||
// Prefix defaultPath elements with the gdev prefix
|
||||
var defaultPath []string
|
||||
if len(cfg.DefaultPath) > 0 {
|
||||
defaultPath = make([]string, len(cfg.DefaultPath))
|
||||
for i, p := range cfg.DefaultPath {
|
||||
defaultPath[i] = prefix + "-" + p
|
||||
}
|
||||
}
|
||||
|
||||
return v0alpha1.ScopeSpec{
|
||||
Title: cfg.Title,
|
||||
Filters: filters,
|
||||
Title: cfg.Title,
|
||||
DefaultPath: defaultPath,
|
||||
Filters: filters,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ Plugin signature verification, also known as _signing_, is a security measure to
|
||||
|
||||
Learn more at [plugin policies](https://grafana.com/legal/plugins/).
|
||||
|
||||
## How does verifiction work?
|
||||
## How does verification work?
|
||||
|
||||
At startup, Grafana verifies the signatures of every plugin in the plugin directory.
|
||||
|
||||
|
||||
+12
-11
@@ -66,17 +66,18 @@ Please refer to plugin documentation to see what RBAC permissions the plugin has
|
||||
|
||||
The following list contains app plugins that have fine-grained RBAC support.
|
||||
|
||||
| App plugin | App plugin ID | App plugin permission documentation |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [Access policies](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/) | `grafana-auth-app` | [RBAC actions for Access Policies](ref:cloud-access-policies-action-definitions) |
|
||||
| [Adaptive Metrics](https://grafana.com/docs/grafana-cloud/cost-management-and-billing/reduce-costs/metrics-costs/control-metrics-usage-via-adaptive-metrics/adaptive-metrics-plugin/) | `grafana-adaptive-metrics-app` | [RBAC actions for Adaptive Metrics](ref:adaptive-metrics-permissions) |
|
||||
| [Cloud Provider](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/) | `grafana-csp-app` | [Cloud Provider Observability role-based access control](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/rbac/) |
|
||||
| [Incident](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/incident/) | `grafana-incident-app` | n/a |
|
||||
| [Kubernetes Monitoring](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/) | `grafana-k8s-app` | [Kubernetes Monitoring role-based access control](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/configuration/control-access/#precision-access-with-rbac-custom-plugin-roles) |
|
||||
| [OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/) | `grafana-oncall-app` | [Configure RBAC for OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/manage/user-and-team-management/#manage-users-and-teams-for-grafana-oncall) |
|
||||
| [Performance Testing (K6)](https://grafana.com/docs/grafana-cloud/testing/k6/) | `k6-app` | [Configure RBAC for K6](https://grafana.com/docs/grafana-cloud/testing/k6/projects-and-users/configure-rbac/) |
|
||||
| [Private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) | `grafana-pdc-app` | n/a |
|
||||
| [Service Level Objective (SLO)](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/) | `grafana-slo-app` | [Configure RBAC for SLO](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/set-up/rbac/) |
|
||||
| App plugin | App plugin ID | App plugin permission documentation |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [Access policies](https://grafana.com/docs/grafana-cloud/account-management/authentication-and-permissions/access-policies/) | `grafana-auth-app` | [RBAC actions for Access Policies](ref:cloud-access-policies-action-definitions) |
|
||||
| [Adaptive Metrics](https://grafana.com/docs/grafana-cloud/cost-management-and-billing/reduce-costs/metrics-costs/control-metrics-usage-via-adaptive-metrics/adaptive-metrics-plugin/) | `grafana-adaptive-metrics-app` | [RBAC actions for Adaptive Metrics](ref:adaptive-metrics-permissions) |
|
||||
| [Cloud Provider](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/) | `grafana-csp-app` | [Cloud Provider Observability role-based access control](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/monitor-cloud-provider/rbac/) |
|
||||
| [Incident](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/incident/) | `grafana-incident-app` | n/a |
|
||||
| [Kubernetes Monitoring](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/) | `grafana-k8s-app` | [Kubernetes Monitoring role-based access control](/docs/grafana-cloud/monitor-infrastructure/kubernetes-monitoring/configuration/control-access/#precision-access-with-rbac-custom-plugin-roles) |
|
||||
| [OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/) | `grafana-oncall-app` | [Configure RBAC for OnCall](https://grafana.com/docs/grafana-cloud/alerting-and-irm/irm/oncall/manage/user-and-team-management/#manage-users-and-teams-for-grafana-oncall) |
|
||||
| [Performance Testing (K6)](https://grafana.com/docs/grafana-cloud/testing/k6/) | `k6-app` | [Configure RBAC for K6](https://grafana.com/docs/grafana-cloud/testing/k6/projects-and-users/configure-rbac/) |
|
||||
| [Private data source connect (PDC)](https://grafana.com/docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/) | `grafana-pdc-app` | n/a |
|
||||
| [Service Level Objective (SLO)](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/) | `grafana-slo-app` | [Configure RBAC for SLO](https://grafana.com/docs/grafana-cloud/alerting-and-irm/slo/set-up/rbac/) |
|
||||
| [Synthetic Monitoring](https://grafana.com/docs/grafana-cloud/testing/synthetic-monitoring/) | `grafana-synthetic-monitoring-app` | [Configure RBAC for Synthetic Monitoring](https://grafana.com/docs/grafana-cloud/testing/synthetic-monitoring/user-and-team-management/) |
|
||||
|
||||
### Revoke fine-grained access from app plugins
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ For the JSON and field usage notes, refer to the [links schema documentation](ht
|
||||
|
||||
### `tags`
|
||||
|
||||
The tags associated with the dashboard:
|
||||
Tags associated with the dashboard. Each tag can be up to 50 characters long.
|
||||
|
||||
` [...string]`
|
||||
|
||||
|
||||
@@ -99,12 +99,27 @@ refs:
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/#query-and-resource-caching
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/#query-and-resource-caching
|
||||
mssql-troubleshoot:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/troubleshooting/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/troubleshooting/
|
||||
postgres:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/postgres/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/postgres/
|
||||
mysql:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mysql/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mysql/
|
||||
---
|
||||
|
||||
# Microsoft SQL Server (MSSQL) data source
|
||||
|
||||
Grafana ships with built-in support for Microsoft SQL Server (MSSQL).
|
||||
You can query and visualize data from any Microsoft SQL Server 2005 or newer, including the Microsoft Azure SQL Database.
|
||||
You can query and visualize data from any Microsoft SQL Server 2005 or newer, including Microsoft Azure SQL Database.
|
||||
|
||||
Use this data source to create dashboards, explore SQL data, and monitor MSSQL-based workloads in real time.
|
||||
|
||||
@@ -113,10 +128,33 @@ The following documentation helps you get started working with the Microsoft SQL
|
||||
- [Configure the Microsoft SQL Server data source](ref:configure-mssql-data-source)
|
||||
- [Microsoft SQL Server query editor](ref:mssql-query-editor)
|
||||
- [Microsoft SQL Server template variables](ref:mssql-template-variables)
|
||||
- [Troubleshoot Microsoft SQL Server data source issues](ref:mssql-troubleshoot)
|
||||
|
||||
## Get the most out of the data source
|
||||
## Supported versions
|
||||
|
||||
After installing and configuring the Microsoft SQL Server data source, you can:
|
||||
This data source supports the following Microsoft SQL Server versions:
|
||||
|
||||
- Microsoft SQL Server 2005 and newer
|
||||
- Microsoft Azure SQL Database
|
||||
- Azure SQL Managed Instance
|
||||
|
||||
Grafana recommends using the latest available service pack for your SQL Server version for optimal compatibility.
|
||||
|
||||
## Key capabilities
|
||||
|
||||
The Microsoft SQL Server data source supports:
|
||||
|
||||
- **Time series queries:** Visualize metrics over time using the built-in time grouping macros.
|
||||
- **Table queries:** Display query results in table format for any valid SQL query.
|
||||
- **Template variables:** Create dynamic dashboards with variable-driven queries.
|
||||
- **Annotations:** Overlay events from SQL Server on your dashboard graphs.
|
||||
- **Alerting:** Create alerts based on SQL Server query results.
|
||||
- **Stored procedures:** Execute stored procedures and visualize results.
|
||||
- **Macros:** Simplify queries with built-in macros for time filtering and grouping.
|
||||
|
||||
## Additional resources
|
||||
|
||||
After configuring the Microsoft SQL Server data source, you can:
|
||||
|
||||
- Create a wide variety of [visualizations](ref:visualizations)
|
||||
- Configure and use [templates and variables](ref:variables)
|
||||
@@ -124,3 +162,8 @@ After installing and configuring the Microsoft SQL Server data source, you can:
|
||||
- Add [annotations](ref:annotate-visualizations)
|
||||
- Set up [alerting](ref:alerting)
|
||||
- Optimize performance with [query caching](ref:query-caching)
|
||||
|
||||
## Related data sources
|
||||
|
||||
- [PostgreSQL](ref:postgres) - For PostgreSQL databases.
|
||||
- [MySQL](ref:mysql) - For MySQL and MariaDB databases.
|
||||
|
||||
@@ -89,6 +89,26 @@ refs:
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-access/configure-authentication/azuread/#enable-azure-ad-oauth-in-grafana
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-access/configure-authentication/azuread/#enable-azure-ad-oauth-in-grafana
|
||||
mssql-query-editor:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/query-editor/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/query-editor/
|
||||
mssql-template-variables:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/template-variables/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/template-variables/
|
||||
alerting:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/alerting/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/alerting-and-irm/alerting/
|
||||
mssql-troubleshoot:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/troubleshooting/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/troubleshooting/
|
||||
---
|
||||
|
||||
# Configure the Microsoft SQL Server data source
|
||||
@@ -97,13 +117,28 @@ This document provides instructions for configuring the Microsoft SQL Server dat
|
||||
|
||||
## Before you begin
|
||||
|
||||
- Grafana comes with a built-in MSSQL data source plugin, eliminating the need to install a plugin.
|
||||
Before configuring the Microsoft SQL Server data source, ensure you have the following:
|
||||
|
||||
- You must have the `Organization administrator` role to configure the MSSQL data source. Organization administrators can also [configure the data source via YAML](#provision-the-data-source) with the Grafana provisioning system.
|
||||
- **Grafana permissions:** You must have the `Organization administrator` role to configure data sources. Organization administrators can also [configure the data source via YAML](#provision-the-data-source) with the Grafana provisioning system.
|
||||
|
||||
- Familiarize yourself with your MSSQL security configuration and gather any necessary security certificates and client keys.
|
||||
- **A running SQL Server instance:** Microsoft SQL Server 2005 or newer, Azure SQL Database, or Azure SQL Managed Instance.
|
||||
|
||||
- Verify that data from MSSQL is being written to your Grafana instance.
|
||||
- **Network access:** Grafana must be able to reach your SQL Server. The default port is `1433`.
|
||||
|
||||
- **Authentication credentials:** Depending on your authentication method, you need one of:
|
||||
- SQL Server login credentials (username and password).
|
||||
- Windows/Kerberos credentials and configuration (not supported in Grafana Cloud).
|
||||
- Azure Entra ID app registration or managed identity.
|
||||
|
||||
- **Security certificates:** If using encrypted connections, gather any necessary TLS/SSL certificates.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
Grafana ships with a built-in Microsoft SQL Server data source plugin. No additional installation is required.
|
||||
{{< /admonition >}}
|
||||
|
||||
{{< admonition type="tip" >}}
|
||||
**Grafana Cloud users:** If your SQL Server is in a private network, you can configure [Private data source connect](ref:private-data-source-connect) to establish connectivity.
|
||||
{{< /admonition >}}
|
||||
|
||||
## Add the MSSQL data source
|
||||
|
||||
@@ -382,3 +417,48 @@ datasources:
|
||||
secureJsonData:
|
||||
password: 'Password!'
|
||||
```
|
||||
|
||||
### Configure with Terraform
|
||||
|
||||
You can configure the Microsoft SQL Server data source using [Terraform](https://www.terraform.io/) with the [Grafana Terraform provider](https://registry.terraform.io/providers/grafana/grafana/latest/docs).
|
||||
|
||||
For more information about provisioning resources with Terraform, refer to the [Grafana as code using Terraform](https://grafana.com/docs/grafana-cloud/developer-resources/infrastructure-as-code/terraform/) documentation.
|
||||
|
||||
#### Terraform example
|
||||
|
||||
The following example creates a basic Microsoft SQL Server data source:
|
||||
|
||||
```hcl
|
||||
resource "grafana_data_source" "mssql" {
|
||||
name = "MSSQL"
|
||||
type = "mssql"
|
||||
url = "localhost:1433"
|
||||
user = "grafana"
|
||||
|
||||
json_data_encoded = jsonencode({
|
||||
database = "grafana"
|
||||
maxOpenConns = 100
|
||||
maxIdleConns = 100
|
||||
maxIdleConnsAuto = true
|
||||
connMaxLifetime = 14400
|
||||
connectionTimeout = 0
|
||||
encrypt = "false"
|
||||
})
|
||||
|
||||
secure_json_data_encoded = jsonencode({
|
||||
password = "Password!"
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
For all available configuration options, refer to the [Grafana provider data source resource documentation](https://registry.terraform.io/providers/grafana/grafana/latest/docs/resources/data_source).
|
||||
|
||||
## Next steps
|
||||
|
||||
After configuring your Microsoft SQL Server data source, you can:
|
||||
|
||||
- [Write queries](ref:mssql-query-editor) using the query editor to explore and visualize your data
|
||||
- [Create template variables](ref:mssql-template-variables) to build dynamic, reusable dashboards
|
||||
- [Add annotations](ref:annotate-visualizations) to overlay SQL Server events on your graphs
|
||||
- [Set up alerting](ref:alerting) to create alert rules based on your SQL Server data
|
||||
- [Troubleshoot issues](ref:mssql-troubleshoot) if you encounter problems with your data source
|
||||
|
||||
@@ -0,0 +1,333 @@
|
||||
---
|
||||
description: Troubleshoot common problems with the Microsoft SQL Server data source in Grafana
|
||||
keywords:
|
||||
- grafana
|
||||
- MSSQL
|
||||
- Microsoft
|
||||
- SQL
|
||||
- troubleshooting
|
||||
- errors
|
||||
labels:
|
||||
products:
|
||||
- cloud
|
||||
- enterprise
|
||||
- oss
|
||||
menuTitle: Troubleshooting
|
||||
title: Troubleshoot Microsoft SQL Server data source issues
|
||||
weight: 400
|
||||
refs:
|
||||
configure-mssql-data-source:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/configure/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/configure/
|
||||
mssql-query-editor:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/query-editor/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana/<GRAFANA_VERSION>/datasources/mssql/query-editor/
|
||||
private-data-source-connect:
|
||||
- pattern: /docs/grafana/
|
||||
destination: /docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/
|
||||
- pattern: /docs/grafana-cloud/
|
||||
destination: /docs/grafana-cloud/connect-externally-hosted/private-data-source-connect/
|
||||
---
|
||||
|
||||
# Troubleshoot Microsoft SQL Server data source issues
|
||||
|
||||
This document provides solutions to common issues you may encounter when configuring or using the Microsoft SQL Server (MSSQL) data source in Grafana.
|
||||
|
||||
## Connection errors
|
||||
|
||||
These errors occur when Grafana cannot establish or maintain a connection to the Microsoft SQL Server.
|
||||
|
||||
### Unable to connect to the server
|
||||
|
||||
**Error message:** "Unable to open tcp connection" or "dial tcp: connection refused"
|
||||
|
||||
**Cause:** Grafana cannot establish a network connection to the SQL Server.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Verify that the SQL Server is running and accessible.
|
||||
1. Check that the host and port are correct in the data source configuration. The default SQL Server port is `1433`.
|
||||
1. Ensure there are no firewall rules blocking the connection between Grafana and SQL Server.
|
||||
1. Verify that SQL Server is configured to allow remote connections.
|
||||
1. For Grafana Cloud, ensure you have configured [Private data source connect](ref:private-data-source-connect) if your SQL Server instance is not publicly accessible.
|
||||
|
||||
### Connection timeout
|
||||
|
||||
**Error message:** "Connection timed out" or "I/O timeout"
|
||||
|
||||
**Cause:** The connection to SQL Server timed out before receiving a response.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Check the network latency between Grafana and SQL Server.
|
||||
1. Verify that SQL Server is not overloaded or experiencing performance issues.
|
||||
1. Increase the **Connection timeout** setting in the data source configuration under **Additional settings**.
|
||||
1. Check if any network devices (load balancers, proxies) are timing out the connection.
|
||||
|
||||
### Encryption-related connection failures
|
||||
|
||||
**Error message:** "TLS handshake failed" or "certificate verify failed"
|
||||
|
||||
**Cause:** There is a mismatch between the encryption settings in Grafana and what the SQL Server supports or requires.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. For older versions of SQL Server (2008, 2008R2), set the **Encrypt** option to **Disable** or **False** in the data source configuration.
|
||||
1. Verify that the SQL Server has a valid SSL certificate if encryption is enabled.
|
||||
1. Check that the certificate is trusted by the Grafana server.
|
||||
1. Ensure you're using the latest available service pack for your SQL Server version for optimal compatibility.
|
||||
|
||||
### Named instance connection issues
|
||||
|
||||
**Error message:** "Cannot connect to named instance" or connection fails when using instance name
|
||||
|
||||
**Cause:** Grafana cannot resolve the SQL Server named instance.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Use the format `hostname\instancename` or `hostname\instancename,port` in the **Host** field.
|
||||
1. Verify that the SQL Server Browser service is running on the SQL Server machine.
|
||||
1. If the Browser service is unavailable, specify the port number directly: `hostname,port`.
|
||||
1. Check that UDP port 1434 is open if using the SQL Server Browser service.
|
||||
|
||||
## Authentication errors
|
||||
|
||||
These errors occur when there are issues with authentication credentials or permissions.
|
||||
|
||||
### Login failed for user
|
||||
|
||||
**Error message:** "Login failed for user 'username'" or "Authentication failed"
|
||||
|
||||
**Cause:** The authentication credentials are invalid or the user doesn't have permission to access the database.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Verify that the username and password are correct.
|
||||
1. Check that the user exists in SQL Server and is enabled.
|
||||
1. Ensure the user has access to the specified database.
|
||||
1. For Windows Authentication, verify that the credentials are in the correct format (`DOMAIN\User`).
|
||||
1. Check that the SQL Server authentication mode allows the type of login you're using (SQL Server Authentication, Windows Authentication, or Mixed Mode).
|
||||
|
||||
### Access denied to database
|
||||
|
||||
**Error message:** "Cannot open database 'dbname' requested by the login"
|
||||
|
||||
**Cause:** The authenticated user doesn't have permission to access the specified database.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Verify that the database name is correct in the data source configuration.
|
||||
1. Ensure the user is mapped to the database with appropriate permissions.
|
||||
1. Grant at least `SELECT` permission on the required tables:
|
||||
|
||||
```sql
|
||||
USE [your_database]
|
||||
GRANT SELECT ON dbo.YourTable TO [your_user]
|
||||
```
|
||||
|
||||
1. Check that the user doesn't have any conflicting permissions from the public role.
|
||||
|
||||
### Windows Authentication (Kerberos) issues
|
||||
|
||||
**Error message:** "Kerberos authentication failed" or "Cannot initialize Kerberos"
|
||||
|
||||
**Cause:** Kerberos configuration is incorrect or incomplete.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Verify that the Kerberos configuration file (`krb5.conf`) path is correct in the data source settings.
|
||||
1. For keytab authentication, ensure the keytab file exists and is readable by Grafana.
|
||||
1. Check that the realm and KDC settings are correct in the Kerberos configuration.
|
||||
1. Verify DNS is correctly resolving the KDC servers.
|
||||
1. Ensure the service principal name (SPN) is registered for the SQL Server instance.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
Kerberos authentication is not supported in Grafana Cloud.
|
||||
{{< /admonition >}}
|
||||
|
||||
### Azure Entra ID authentication errors
|
||||
|
||||
**Error message:** "AADSTS error codes" or "Azure AD authentication failed"
|
||||
|
||||
**Cause:** Azure Entra ID (formerly Azure AD) authentication is misconfigured.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. For **App Registration** authentication:
|
||||
- Verify the tenant ID, client ID, and client secret are correct.
|
||||
- Ensure the app registration has been added as a user in the Azure SQL database.
|
||||
- Check that the client secret hasn't expired.
|
||||
|
||||
1. For **Managed Identity** authentication:
|
||||
- Verify `managed_identity_enabled = true` is set in the Grafana server configuration.
|
||||
- Ensure the managed identity has been added to the Azure SQL database.
|
||||
- Confirm the Azure resource hosting Grafana has managed identity enabled.
|
||||
|
||||
1. For **Current User** authentication:
|
||||
- Ensure `user_identity_enabled = true` is set in the Grafana server configuration.
|
||||
- Verify the app registration is configured to issue both Access Tokens and ID Tokens.
|
||||
- Check that the required API permissions are configured (`user_impersonation` for Azure SQL).
|
||||
|
||||
For detailed Azure authentication configuration, refer to [Configure the Microsoft SQL Server data source](ref:configure-mssql-data-source).
|
||||
|
||||
## Query errors
|
||||
|
||||
These errors occur when there are issues with query syntax or configuration.
|
||||
|
||||
### Time column not found or invalid
|
||||
|
||||
**Error message:** "Could not find time column" or time series visualization shows no data
|
||||
|
||||
**Cause:** The query doesn't return a properly formatted `time` column for time series visualization.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Ensure your query includes a column named `time` when using the **Time series** format.
|
||||
1. Use the `$__time()` macro to rename your date column: `$__time(your_date_column)`.
|
||||
1. Verify the time column is of a valid SQL date/time type (`datetime`, `datetime2`, `date`) or contains Unix epoch values.
|
||||
1. Ensure the result set is sorted by the time column using `ORDER BY`.
|
||||
|
||||
### Macro expansion errors
|
||||
|
||||
**Error message:** "Error parsing query" or macros appear unexpanded in the query
|
||||
|
||||
**Cause:** Grafana macros are being used incorrectly.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Verify macro syntax: use `$__timeFilter(column)` not `$_timeFilter(column)`.
|
||||
1. Macros don't work inside stored procedures—use explicit date parameters instead.
|
||||
1. Check that the column name passed to macros exists in your table.
|
||||
1. View the expanded query by clicking **Generated SQL** after running the query to debug macro expansion.
|
||||
|
||||
### Timezone and time shift issues
|
||||
|
||||
**Cause:** Time series data appears shifted or doesn't align with expected times.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Store timestamps in UTC in your database to avoid timezone issues.
|
||||
1. Time macros (`$__time`, `$__timeFilter`, etc.) always expand to UTC values.
|
||||
1. If your timestamps are stored in local time, convert them to UTC in your query:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
your_datetime_column AT TIME ZONE 'Your Local Timezone' AT TIME ZONE 'UTC' AS time,
|
||||
value
|
||||
FROM your_table
|
||||
```
|
||||
|
||||
1. Don't pass timezone parameters to time macros—they're not supported.
|
||||
|
||||
### Query returns too many rows
|
||||
|
||||
**Error message:** "Result set too large" or browser becomes unresponsive
|
||||
|
||||
**Cause:** The query returns more data than can be efficiently processed.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Add time filters using `$__timeFilter(column)` to limit data to the dashboard time range.
|
||||
1. Use aggregations (`AVG`, `SUM`, `COUNT`) with `GROUP BY` instead of returning raw rows.
|
||||
1. Add a `TOP` clause to limit results: `SELECT TOP 1000 ...`.
|
||||
1. Use the `$__timeGroup()` macro to aggregate data into time intervals.
|
||||
|
||||
### Stored procedure returns no data
|
||||
|
||||
**Cause:** Stored procedure output isn't being captured correctly.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Ensure the stored procedure uses `SELECT` statements, not just variable assignments.
|
||||
1. Remove `SET NOCOUNT ON` if present, or ensure it's followed by a `SELECT` statement.
|
||||
1. Verify the stored procedure parameters are being passed correctly.
|
||||
1. Test the stored procedure directly in SQL Server Management Studio with the same parameters.
|
||||
|
||||
For more information on using stored procedures, refer to the [query editor documentation](ref:mssql-query-editor).
|
||||
|
||||
## Performance issues
|
||||
|
||||
These issues relate to slow queries or high resource usage.
|
||||
|
||||
### Slow query execution
|
||||
|
||||
**Cause:** Queries take a long time to execute.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Reduce the dashboard time range to limit data volume.
|
||||
1. Add indexes to columns used in `WHERE` clauses and time filters.
|
||||
1. Use aggregations instead of returning individual rows.
|
||||
1. Increase the **Min time interval** setting to reduce the number of data points.
|
||||
1. Review the query execution plan in SQL Server Management Studio to identify bottlenecks.
|
||||
|
||||
### Connection pool exhaustion
|
||||
|
||||
**Error message:** "Too many connections" or "Connection pool exhausted"
|
||||
|
||||
**Cause:** Too many concurrent connections to the database.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Increase the **Max open** connection limit in the data source configuration.
|
||||
1. Enable **Auto max idle** to automatically manage idle connections.
|
||||
1. Reduce the number of panels querying the same data source simultaneously.
|
||||
1. Check for long-running queries that might be holding connections.
|
||||
|
||||
## Other common issues
|
||||
|
||||
The following issues don't produce specific error messages but are commonly encountered.
|
||||
|
||||
### System databases appear in queries
|
||||
|
||||
**Cause:** Queries accidentally access system databases.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. The query editor automatically excludes `tempdb`, `model`, `msdb`, and `master` from the database dropdown.
|
||||
1. Always specify the database in your data source configuration to restrict access.
|
||||
1. Ensure the database user only has permissions on the intended database.
|
||||
|
||||
### Template variable queries fail
|
||||
|
||||
**Cause:** Variable queries return unexpected results or errors.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Verify the variable query syntax is valid SQL that returns a single column.
|
||||
1. Check that the data source connection is working.
|
||||
1. Ensure the user has permission to access the tables referenced in the variable query.
|
||||
1. Test the query in the query editor before using it as a variable query.
|
||||
|
||||
### Data appears incorrect or misaligned
|
||||
|
||||
**Cause:** Data formatting or type conversion issues.
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. Use explicit column aliases to ensure consistent naming: `SELECT value AS metric`.
|
||||
1. Verify numeric columns are actually numeric types, not strings.
|
||||
1. Check for `NULL` values that might affect aggregations.
|
||||
1. Use the `FILL` option in `$__timeGroup()` macro to handle missing data points.
|
||||
|
||||
## Get additional help
|
||||
|
||||
If you continue to experience issues after following this troubleshooting guide:
|
||||
|
||||
1. Check the [Grafana community forums](https://community.grafana.com/) for similar issues.
|
||||
1. Review the [Grafana GitHub issues](https://github.com/grafana/grafana/issues) for known bugs.
|
||||
1. Enable debug logging in Grafana to capture detailed error information.
|
||||
1. Check SQL Server logs for additional error details.
|
||||
1. Contact Grafana Support if you're an Enterprise or Cloud customer.
|
||||
|
||||
When reporting issues, include:
|
||||
|
||||
- Grafana version
|
||||
- SQL Server version
|
||||
- Error messages (redact sensitive information)
|
||||
- Steps to reproduce
|
||||
- Relevant query examples (redact sensitive data)
|
||||
@@ -231,6 +231,10 @@ JSON Body schema:
|
||||
- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the folder under which the dashboard should be created.
|
||||
- **spec** – The dashboard json.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
Custom labels and annotations in the metadata field are supported on some instances, with full support planned for all instances when these APIs reach general availability. If they are not yet supported on your instance, they will be ignored.
|
||||
{{< /admonition >}}
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
@@ -521,6 +525,10 @@ JSON Body schema:
|
||||
- **metadata.annotations.grafana.app/message** - Optional field, to set a commit message for the version history.
|
||||
- **spec** – The dashboard json.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
Custom labels and annotations in the metadata field are supported on some instances, with full support planned for all instances when these APIs reach general availability. If they are not yet supported on your instance, they will be ignored.
|
||||
{{< /admonition >}}
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
|
||||
@@ -259,6 +259,10 @@ JSON Body schema:
|
||||
- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the parent folder under which the folder should be created. Requires nested folders to be enabled.
|
||||
- **spec.title** – The title of the folder.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
Custom labels and annotations in the metadata field are supported on some instances, with full support planned for all instances when these APIs reach general availability. If they are not yet supported on your instance, they will be ignored.
|
||||
{{< /admonition >}}
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
@@ -337,6 +341,10 @@ JSON Body schema:
|
||||
- **metadata.annotations.grafana.app/folder** - Optional field, the unique identifier of the parent folder under which the folder should be - update this to move the folder under a different parent folder. Requires nested folders to be enabled.
|
||||
- **spec.title** – The title of the folder.
|
||||
|
||||
{{< admonition type="note" >}}
|
||||
Custom labels and annotations in the metadata field are supported on some instances, with full support planned for all instances when these APIs reach general availability. If they are not yet supported on your instance, they will be ignored.
|
||||
{{< /admonition >}}
|
||||
|
||||
**Example Response**:
|
||||
|
||||
```http
|
||||
|
||||
@@ -41,7 +41,8 @@ Query parameters:
|
||||
- `sortDirection`: Sort order of elements. Use `alpha-asc` for ascending and `alpha-desc` for descending sort order.
|
||||
- `typeFilter`: A comma separated list of types to filter the elements by.
|
||||
- `excludeUid`: Element UID to exclude from search results.
|
||||
- `folderFilter`: A comma separated list of folder IDs to filter the elements by.
|
||||
- `folderFilter`: **Deprecated.** A comma separated list of folder IDs to filter the elements by. Use `folderFilterUIDs` instead.
|
||||
- `folderFilterUIDs`: A comma separated list of folder UIDs to filter the elements by.
|
||||
- `perPage`: The number of results per page; default is 100.
|
||||
- `page`: The page for a set of records, given that only `perPage` records are returned at a time. Numbering starts at `1`.
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user