Merge remote-tracking branch 'origin/main' into team-user-folder-validation
CodeQL checks / Detect whether code changed (push) Waiting to run Details
CodeQL checks / Analyze (actions) (push) Blocked by required conditions Details
CodeQL checks / Analyze (go) (push) Blocked by required conditions Details
CodeQL checks / Analyze (javascript) (push) Blocked by required conditions Details

This commit is contained in:
Ryan McKinley 2025-10-07 14:27:59 +03:00
commit 829770e6eb
736 changed files with 24344 additions and 7367 deletions

View File

@ -1,13 +1,13 @@
[build]
bin = "./bin/grafana"
bin = "./bin/grafana-air"
args_bin = ["server", "-profile", "-profile-addr=127.0.0.1", "-profile-port=6000", "-profile-block-rate=1", "-profile-mutex-rate=5", "-packaging=dev", "cfg:app_mode=development"]
cmd = "make GO_BUILD_DEV=1 build-backend"
cmd = "make GO_BUILD_DEV=1 build-air"
exclude_regex = ["_test.go", "_gen.go"]
exclude_unchanged = true
follow_symlink = true
include_dir = ["apps", "conf", "devenv/dev-dashboards", "pkg", "public/views"]
include_dir = ["apps", "conf", "pkg", "public/views"]
include_ext = ["go", "ini", "toml", "html", "json"]
stop_on_error = false
stop_on_error = true
send_interrupt = true
kill_delay = 500

5
.github/CODEOWNERS vendored
View File

@ -33,6 +33,8 @@
# START Technical documentation
/.vale.ini @grafana/docs-tooling
/AGENTS.md @grafana/docs-tooling
# `make docs` procedure and related workflows are owned @grafana/docs-tooling. Slack #docs.
/docs/ @grafana/docs-tooling
@ -86,6 +88,7 @@
/apps/preferences/ @grafana/grafana-app-platform-squad @grafana/grafana-frontend-platform
/apps/shorturl/ @grafana/sharing-squad
/apps/secret/ @grafana/grafana-operator-experience-squad
/apps/scope/ @grafana/grafana-operator-experience-squad
/apps/investigations/ @fcjack @matryer @svennergr
/apps/advisor/ @grafana/plugins-platform-backend
/apps/iam/ @grafana/access-squad
@ -627,6 +630,7 @@
/packages/grafana-runtime/rollup.config.ts @grafana/grafana-frontend-platform
/packages/grafana-runtime/src/index.ts @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend
/packages/grafana-runtime/src/internal/index.ts @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend
/packages/grafana-runtime/src/internal/openFeature @grafana/grafana-frontend-platform
/packages/grafana-runtime/src/unstable.ts @grafana/grafana-frontend-platform @grafana/plugins-platform-frontend
/packages/grafana-runtime/tsconfig.build.json @grafana/grafana-frontend-platform
/packages/grafana-runtime/tsconfig.json @grafana/grafana-frontend-platform
@ -1279,6 +1283,7 @@ embed.go @grafana/grafana-as-code
/.github/license_finder.yaml @bergquist
/.github/actionlint.yaml @grafana/grafana-developer-enablement-squad
/.github/workflows/pr-test-docker.yml @grafana/grafana-developer-enablement-squad
/.github/workflows/update-schema-types.yml @grafana/plugins-platform-frontend
# Generated files not requiring owner approval
/packages/grafana-data/src/types/featureToggles.gen.ts @grafanabot

View File

@ -11,6 +11,8 @@ permissions: {}
jobs:
test:
name: Feature toggles documentation is in sync with source
runs-on: ubuntu-latest
permissions:

View File

@ -149,19 +149,11 @@ jobs:
needs:
- build-grafana
steps:
- id: vault-secrets
uses: grafana/shared-workflows/actions/get-vault-secrets@main
- id: get-github-token
name: "create github app token"
uses: grafana/shared-workflows/actions/create-github-app-token@eb02241ed0a92aff205feab8ac3afcdf51c757c8 # create-github-app-token-v0.2.0
with:
repo_secrets: |
GRAFANA_DELIVERY_BOT_APP_PEM=delivery-bot-app:PRIVATE_KEY
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a
with:
app_id: ${{ vars.DELIVERY_BOT_APP_ID }}
private_key: ${{ env.GRAFANA_DELIVERY_BOT_APP_PEM }}
repositories: '["grafana"]'
permissions: '{"checks": "write"}'
github_app: "delivery-bot-app"
- uses: grafana/shared-workflows/actions/login-to-gar@main
id: login-to-gar
with:
@ -184,7 +176,7 @@ jobs:
echo "IMAGE=${DOCKER_IMAGE}" >> "$GITHUB_ENV"
- name: Add PR status check
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
GH_TOKEN: ${{ steps.get-github-token.outputs.token }}
SHA: ${{ github.event.pull_request.head.sha }}
run: |
gh api \

View File

@ -39,12 +39,14 @@ permissions: {}
jobs:
# If called with version_type 'canary' or 'stable', build + publish to NPM
# If called with version_type 'nightly', just tag the given version with nightly tag. It was already published by the canary build.
# If called with version_type 'nightly', do nothing (we're not yet tagging them with the nightly tag)
publish:
name: Publish NPM packages
runs-on: github-hosted-ubuntu-x64-small
if: inputs.version_type == 'canary' || inputs.version_type == 'stable'
# Required for this workflow to have permission to publish NPM packages
environment: npm-publish
permissions:
contents: read
id-token: write
@ -130,18 +132,3 @@ jobs:
env:
NPM_TAG: ${{ steps.npm-tag.outputs.NPM_TAG }}
run: ./scripts/publish-npm-packages.sh --dist-tag "$NPM_TAG" --registry 'https://registry.npmjs.org/'
# TODO: finish this step
tag-nightly:
name: Tag nightly release
runs-on: github-hosted-ubuntu-x64-small
if: inputs.version_type == 'nightly'
steps:
- name: Checkout workflow ref
uses: actions/checkout@v4
with:
persist-credentials: false
# TODO: tag the given release with nightly

View File

@ -39,9 +39,10 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version-file: '.nvmrc'
package-manager-cache: false # too large for GH's cache limits :-(
- run: yarn install --immutable --check-cache
- name: Install Playwright browsers
run: npx playwright install --with-deps
@ -68,9 +69,10 @@ jobs:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v4
- uses: actions/setup-node@v5
with:
node-version-file: '.nvmrc'
package-manager-cache: false # too large for GH's cache limits :-(
- run: yarn install --immutable --check-cache
- name: Install Playwright browsers
run: npx playwright install --with-deps

View File

@ -0,0 +1,22 @@
name: Update Schema Types
on:
push:
branches:
- main
paths:
- docs/sources/developers/plugins/plugin.schema.json
workflow_dispatch:
# These permissions are needed to assume roles from Github's OIDC.
permissions:
contents: read
id-token: write
jobs:
bundle-schema-types:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: grafana/plugin-actions/bundle-schema-types@main

View File

@ -6,9 +6,13 @@ on:
paths:
- '**/*.cue'
permissions: {}
jobs:
main:
runs-on: "ubuntu-latest"
permissions:
contents: read # clone repository
steps:
- name: "Checkout Grafana repo"
uses: "actions/checkout@v5"

297
AGENTS.md Normal file
View File

@ -0,0 +1,297 @@
# AGENTS.md
<!-- docs-ai-begin -->
<!-- version: 1.1.0 -->
## Documentation
Instructions for documentation authoring in Markdown files.
DOCS.md contains all the Docs AI toolkit docs in one file.
## Role
Act as an experienced software engineer and technical writer for Grafana Labs.
Write for software developers and engineers who understand general programming concepts.
Focus on practical implementation and clear problem-solving guidance.
### Grafana
Use full product names on first mention, then short names:
- Grafana Alloy (full), Alloy (short)
- Grafana Beyla (full), Beyla (short)
Use "OpenTelemetry Collector" on first mention, then "Collector" for subsequent references.
Keep full name for distributions, headings, and links.
Always use "Grafana Cloud" in full.
Use complete terms:
- "OpenTelemetry" (not "OTel")
- "Kubernetes" (not "K8s")
Present observability signals in order: metrics, logs, traces, and profiles.
Focus content on Grafana solutions when discussing integrations or migrations.
## Style
### Structure
Structure articles into sections with headings.
Leave Markdown front matter content between two triple dashes `---`.
The front matter YAML `title` and the content h1 (#) heading should be the same.
Make sure there's an h1 heading in the content; this redundancy is required.
Always include copy after a heading or between headings, for example:
```markdown
## Heading
Immediately followed by copy and not another heading.
## Sub heading
```
The immediate copy after a heading should introduce and provide an overview of what's covered in the section.
Start articles with an introduction that covers the goal of the article. Example goals:
- Learn concepts
- Set up or install something
- Configure something
- Use a product to solve a business problem
- Troubleshoot a problem
- Integrate with other software or systems
- Migrate from one thing to another
- Refer to APIs or reference documentation
Follow the goal with a list of prerequisites, for example:
```markdown
Before you begin, ensure you have the following:
- <Prerequisite 1>
- <Prerequisite 2>
- ...
```
Suggest and link to next steps and related resources at the end of the article, for example:
- Learn more about A, B, C
- Configure X
- Use X to achieve Y
- Use X to achieve Z
- Project homepage or documentation
- Project repository (for example, GitHub, GitLab)
- Project package (for example, pip or NPM)
You don't need to use the "Refer to..." syntax for next steps; use the link text directly.
### Copy
Write simple, direct copy with short sentences and paragraphs.
Use contractions:
- it's, isn't, that's, you're, don't
Choose simple words:
- use (not utilize)
- help (not assist)
- show (not demonstrate)
Write with verbs and nouns. Use minimal adjectives except when describing Grafana Labs products.
## Tense
Write in present simple tense.
Avoid present continuous tense.
Only write in future tense to show future actions.
### Voice
Always write in an active voice.
Change passive voice to active voice.
### Perspective
Address users as "you".
Use second person perspective consistently.
### Wordlist
Use allowlist/blocklist instead of whitelist/blacklist.
Use primary/secondary instead of master/slave.
Use "refer to" instead of "see", "consult", "check out", and other phrases.
### Formatting
Use sentence case for titles and headings.
Use inline Markdown links: [Link text](https://example.com).
Link to other sections using descriptive phrases that include the section name:
"For setup details, refer to the [Lists](#lists) section."
Bold text with two asterisks: **bold**
Emphasize text with one underscore: _italics_
Format UI elements using sentence case as they appear:
- Click **Submit**.
- Navigate to **User settings**.
- Configure **Alerting rules**.
### Lists
Write complete sentences for lists:
- Works with all languages and frameworks (correct)
- All languages and frameworks (incorrect)
Use dashes for unordered lists.
Bold keywords at list start and follow with a colon.
### Images
Include descriptive alt text that conveys the essential information or purpose.
Write alt text without "Image of..." or "Picture of..." prefixes.
### Code
Use single code backticks for:
- user input
- placeholders in markdown, for example _`<PLACEHOLDER_NAME>`_
- files and directories, for example `/opt/file.md`
- source code keywords and identifiers,
for example variables, function and class names
- configuration options and values, for example `PORT` and `80`
- status codes, for example `404`
Use triple code backticks followed by the syntax for code blocks, for example:
```javascript
console.log('Hello World!');
```
Introduce each code block with a short description.
End the introduction with a colon if the code sample follows it, for example:
```markdown
The code sample outputs "Hello World!" to the browser console:
<CODE_BLOCK>
```
Use descriptive placeholder names in code samples.
Use uppercase letters with underscores to separate words in placeholders,
for example:
```sh
OTEL_RESOURCE_ATTRIBUTES="service.name=<SERVICE_NAME>
OTEL_EXPORTER_OTLP_ENDPOINT=<OTLP_ENDPOINT>
```
The placeholder includes the name and the less than and greater than symbols,
for example <PLACEHOLDER_NAME>.
If the placeholder is markdown emphasize it with underscores,
for example _`<PLACEHOLDER_NAME>`_.
In code blocks use the placeholder without additional backticks or emphasis,
for example <PLACEHOLDER_NAME>.
Provide an explanation for each placeholder,
typically in the text following the code block or in a configuration section.
Follow code samples with an explanation
and configuration options for placeholders, for example:
```markdown
<CODE_BLOCK>
This code sets required environment variables
to send OTLP data to an OTLP endpoint.
To configure the code refer to the configuration section.
<CONFIGURATION>
```
Put configuration for a code block after the code block.
## APIs
When documenting API endpoints specify the HTTP method,
for example `GET`, `POST`, `PUT`, `DELETE`.
Provide the full request path, using backticks.
Use backticks for parameter names and example values.
Use placeholders like `{userId}` for path parameters, for example:
- To retrieve user details, make a `GET` request to `/api/v1/users/{userId}`.
### CLI commands
When presenting CLI commands and their output,
introduce the command with a brief explanation of its purpose.
Clearly distinguish the command from its output.
For commands, use `sh` to specify the code block language.
For output, use a generic specifier like `text`, `console`,
or `json`/`yaml` if the output is structured.
For example:
```markdown
To list all running pods in the `default` namespace, use the following command:
<CODE_BLOCK>
```
The output will resemble the following:
```text
NAME READY STATUS RESTARTS AGE
my-app-deployment-7fdb6c5f65-abcde 1/1 Running 0 2d1h
another-service-pod-xyz123 2/2 Running 0 5h30m
```
### Shortcodes
Leave Hugo shortcodes in the content when editing.
Use our custom admonition Hugo shortcode for notes, cautions, or warnings,
with `<TYPE>` as "note", "caution", or "warning":
```markdown
{{< admonition type="<TYPE>" >}}
...
{{< /admonition >}}
```
Use admonitions sparingly.
Only include exceptional information in admonitions.
<!-- docs-ai-end -->

View File

@ -99,6 +99,7 @@ COPY apps/correlations apps/correlations
COPY apps/preferences apps/preferences
COPY apps/provisioning apps/provisioning
COPY apps/secret apps/secret
COPY apps/scope apps/scope
COPY apps/investigations apps/investigations
COPY apps/advisor apps/advisor
COPY apps/dashboard apps/dashboard

View File

@ -17,6 +17,7 @@ GO_RACE_FLAG := $(if $(GO_RACE),-race)
GO_BUILD_FLAGS += $(if $(GO_BUILD_DEV),-dev)
GO_BUILD_FLAGS += $(if $(GO_BUILD_TAGS),-build-tags=$(GO_BUILD_TAGS))
GO_BUILD_FLAGS += $(GO_RACE_FLAG)
GO_BUILD_FLAGS += $(if $(GO_BUILD_CGO),-cgo-enabled=$(GO_BUILD_CGO))
GO_TEST_FLAGS += $(if $(GO_BUILD_TAGS),-tags=$(GO_BUILD_TAGS))
GIT_BASE = remotes/origin/main
@ -245,6 +246,10 @@ build-backend: ## Build Grafana backend.
@echo "build backend"
$(GO) run build.go $(GO_BUILD_FLAGS) build-backend
.PHONY: build-air
build-air: build-backend
@cp ./bin/grafana ./bin/grafana-air
.PHONY: build-server
build-server: ## Build Grafana server.
@echo "build server"

View File

@ -7,6 +7,7 @@ import (
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/k8s"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana-app-sdk/simple"
advisorv0alpha1 "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
@ -48,10 +49,12 @@ func New(cfg app.Config) (app.App, error) {
Name: "advisor",
KubeConfig: cfg.KubeConfig,
InformerConfig: simple.AppInformerConfig{
InformerOptions: operator.InformerOptions{
ErrorHandler: func(ctx context.Context, err error) {
log.WithContext(ctx).Error("Informer processing error", "error", err)
},
},
},
ManagedKinds: []simple.AppManagedKind{
{
Kind: advisorv0alpha1.CheckKind(),

View File

@ -3,14 +3,14 @@ module github.com/grafana/grafana/apps/alerting/alertenrichment
go 1.24.6
require (
github.com/grafana/grafana-app-sdk v0.45.0
github.com/grafana/grafana-app-sdk v0.46.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250901080157-a0280d701b28
k8s.io/apimachinery v0.34.1
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
)
require (
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect
@ -28,7 +28,7 @@ require (
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.44.0 // indirect
golang.org/x/text v0.29.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect

View File

@ -2,8 +2,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
@ -21,8 +21,8 @@ github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7O
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/grafana/grafana-app-sdk v0.45.0 h1:niFqYovxuw9vnUB9qoxEgmupqriG7Gns9ZGwB2uuOyE=
github.com/grafana/grafana-app-sdk v0.45.0/go.mod h1:1pYGEBrgG8i6pKmmsNXvtAr15jZ4iLtyHU4yj7T6XaI=
github.com/grafana/grafana-app-sdk v0.46.0 h1:gvzQvCQgZJ/73BfAcbDt/6TAMhnVikVPxZt/UwDl+oc=
github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250901080157-a0280d701b28 h1:PgMfX4OPENz/iXmtDDIW9+poZY4UD0hhmXm7flVclDo=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250901080157-a0280d701b28/go.mod h1:av5N0Naq+8VV9MLF7zAkihy/mVq5UbS2EvRSJukDHlY=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@ -91,8 +91,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -5,6 +5,7 @@ metadata:
spec:
appName: alerting-notifications
group: notifications.alerting.grafana.app
preferredVersion: v0alpha1
versions:
- kinds:
- conversion: false

View File

@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/alerting/notifications
go 1.24.6
require (
github.com/grafana/grafana-app-sdk v0.45.0
github.com/grafana/grafana-app-sdk v0.46.0
github.com/grafana/grafana-app-sdk/logging v0.45.0
k8s.io/apimachinery v0.34.1
k8s.io/apiserver v0.34.1
@ -19,7 +19,7 @@ require (
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
@ -94,7 +94,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@ -21,8 +21,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
@ -69,8 +69,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/grafana-app-sdk v0.45.0 h1:niFqYovxuw9vnUB9qoxEgmupqriG7Gns9ZGwB2uuOyE=
github.com/grafana/grafana-app-sdk v0.45.0/go.mod h1:1pYGEBrgG8i6pKmmsNXvtAr15jZ4iLtyHU4yj7T6XaI=
github.com/grafana/grafana-app-sdk v0.46.0 h1:gvzQvCQgZJ/73BfAcbDt/6TAMhnVikVPxZt/UwDl+oc=
github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc=
github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xuHKJjnO95/2pGOoA8w=
github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs/O40yoNK9vmy4rhUGBVyMf1lISBGtXRpsu/Qu/o=
@ -284,8 +284,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.
google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -36,6 +36,7 @@ var (
var appManifestData = app.ManifestData{
AppName: "alerting-notifications",
Group: "notifications.alerting.grafana.app",
PreferredVersion: "v0alpha1",
Versions: []app.ManifestVersion{
{
Name: "v0alpha1",

View File

@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/simple"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis"
@ -22,10 +23,12 @@ func New(cfg app.Config) (app.App, error) {
Name: "alerting.notification",
KubeConfig: cfg.KubeConfig,
InformerConfig: simple.AppInformerConfig{
InformerOptions: operator.InformerOptions{
ErrorHandler: func(ctx context.Context, err error) {
logging.DefaultLogger.With("error", err).Error("Informer processing error")
},
},
},
ManagedKinds: managedKinds,
}

View File

@ -5,6 +5,7 @@ metadata:
spec:
appName: alerting
group: rules.alerting.grafana.app
preferredVersion: v0alpha1
versions:
- kinds:
- conversion: false

View File

@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/alerting/rules
go 1.24.4
require (
github.com/grafana/grafana-app-sdk v0.45.0
github.com/grafana/grafana-app-sdk v0.46.0
github.com/grafana/grafana-app-sdk/logging v0.45.0
k8s.io/apimachinery v0.34.1
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
@ -15,7 +15,7 @@ require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
@ -77,7 +77,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.34.1 // indirect

View File

@ -10,8 +10,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@ -46,8 +46,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/grafana-app-sdk v0.45.0 h1:niFqYovxuw9vnUB9qoxEgmupqriG7Gns9ZGwB2uuOyE=
github.com/grafana/grafana-app-sdk v0.45.0/go.mod h1:1pYGEBrgG8i6pKmmsNXvtAr15jZ4iLtyHU4yj7T6XaI=
github.com/grafana/grafana-app-sdk v0.46.0 h1:gvzQvCQgZJ/73BfAcbDt/6TAMhnVikVPxZt/UwDl+oc=
github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc=
github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xuHKJjnO95/2pGOoA8w=
github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
@ -203,8 +203,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -30,6 +30,7 @@ var (
var appManifestData = app.ManifestData{
AppName: "alerting",
Group: "rules.alerting.grafana.app",
PreferredVersion: "v0alpha1",
Versions: []app.ManifestVersion{
{
Name: "v0alpha1",

View File

@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/simple"
"github.com/grafana/grafana/apps/alerting/rules/pkg/apis"
@ -22,10 +23,12 @@ func New(cfg app.Config) (app.App, error) {
Name: "alerting.rules",
KubeConfig: cfg.KubeConfig,
InformerConfig: simple.AppInformerConfig{
InformerOptions: operator.InformerOptions{
ErrorHandler: func(ctx context.Context, err error) {
logging.DefaultLogger.With("error", err).Error("Informer processing error")
},
},
},
ManagedKinds: managedKinds,
}

View File

@ -6,4 +6,5 @@ generate: install-app-sdk update-app-sdk
--source=./kinds/ \
--gogenpath=./pkg/apis \
--grouping=group \
--genoperatorstate=false \
--defencoding=none

View File

@ -5,7 +5,7 @@ go 1.24.0
toolchain go1.24.6
require (
github.com/grafana/grafana-app-sdk v0.45.0
github.com/grafana/grafana-app-sdk v0.46.0
github.com/grafana/grafana-app-sdk/logging v0.45.0
k8s.io/apimachinery v0.34.1
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
@ -17,7 +17,7 @@ require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
@ -79,7 +79,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.34.1 // indirect

View File

@ -10,8 +10,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@ -46,8 +46,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/grafana-app-sdk v0.45.0 h1:niFqYovxuw9vnUB9qoxEgmupqriG7Gns9ZGwB2uuOyE=
github.com/grafana/grafana-app-sdk v0.45.0/go.mod h1:1pYGEBrgG8i6pKmmsNXvtAr15jZ4iLtyHU4yj7T6XaI=
github.com/grafana/grafana-app-sdk v0.46.0 h1:gvzQvCQgZJ/73BfAcbDt/6TAMhnVikVPxZt/UwDl+oc=
github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc=
github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xuHKJjnO95/2pGOoA8w=
github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
@ -203,8 +203,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type CorrelationClient struct {
@ -76,24 +75,6 @@ func (c *CorrelationClient) Patch(ctx context.Context, identifier resource.Ident
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *CorrelationClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus CorrelationStatus, opts resource.UpdateOptions) (*Correlation, error) {
return c.client.Update(ctx, &Correlation{
TypeMeta: metav1.TypeMeta{
Kind: CorrelationKind().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 *CorrelationClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@ -21,8 +21,6 @@ type Correlation struct {
// Spec is the spec of the Correlation
Spec CorrelationSpec `json:"spec" yaml:"spec"`
Status CorrelationStatus `json:"status" yaml:"status"`
}
func (o *Correlation) GetSpec() any {
@ -39,15 +37,11 @@ func (o *Correlation) SetSpec(spec any) error {
}
func (o *Correlation) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
return map[string]any{}
}
func (o *Correlation) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
@ -55,13 +49,6 @@ func (o *Correlation) GetSubresource(name string) (any, bool) {
func (o *Correlation) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(CorrelationStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type CorrelationStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
@ -233,7 +220,6 @@ func (o *Correlation) DeepCopyInto(dst *Correlation) {
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
@ -305,15 +291,3 @@ func (s *CorrelationSpec) DeepCopy() *CorrelationSpec {
func (s *CorrelationSpec) DeepCopyInto(dst *CorrelationSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of CorrelationStatus
func (s *CorrelationStatus) DeepCopy() *CorrelationStatus {
cpy := &CorrelationStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies CorrelationStatus into another CorrelationStatus object
func (s *CorrelationStatus) DeepCopyInto(dst *CorrelationStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@ -1,44 +0,0 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
// +k8s:openapi-gen=true
type CorrelationstatusOperatorState 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 CorrelationStatusOperatorStateState `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"`
}
// NewCorrelationstatusOperatorState creates a new CorrelationstatusOperatorState object.
func NewCorrelationstatusOperatorState() *CorrelationstatusOperatorState {
return &CorrelationstatusOperatorState{}
}
// +k8s:openapi-gen=true
type CorrelationStatus 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]CorrelationstatusOperatorState `json:"operatorStates,omitempty"`
// additionalFields is reserved for future use
AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"`
}
// NewCorrelationStatus creates a new CorrelationStatus object.
func NewCorrelationStatus() *CorrelationStatus {
return &CorrelationStatus{}
}
// +k8s:openapi-gen=true
type CorrelationStatusOperatorStateState string
const (
CorrelationStatusOperatorStateStateSuccess CorrelationStatusOperatorStateState = "success"
CorrelationStatusOperatorStateStateInProgress CorrelationStatusOperatorStateState = "in_progress"
CorrelationStatusOperatorStateStateFailed CorrelationStatusOperatorStateState = "failed"
)

View File

@ -10,17 +10,16 @@ import (
"fmt"
"strings"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kube-openapi/pkg/spec3"
"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"
v0alpha1 "github.com/grafana/grafana/apps/correlations/pkg/apis/correlation/v0alpha1"
)
var (
rawSchemaCorrelationv0alpha1 = []byte(`{"ConfigSpec":{"additionalProperties":false,"description":"there was a deprecated field here called type, we will need to move that for conversion and provisioning","properties":{"field":{"type":"string"},"target":{"$ref":"#/components/schemas/TargetSpec"},"transformations":{"items":{"$ref":"#/components/schemas/TransformationSpec"},"type":"array"}},"required":["field","target"],"type":"object"},"Correlation":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"CorrelationType":{"enum":["query","external"],"type":"string"},"DataSourceRef":{"additionalProperties":false,"properties":{"group":{"description":"same as pluginId","type":"string"},"name":{"description":"same as grafana uid","type":"string"}},"required":["group","name"],"type":"object"},"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"},"TargetSpec":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"TransformationSpec":{"additionalProperties":false,"properties":{"expression":{"type":"string"},"field":{"type":"string"},"mapValue":{"type":"string"},"type":{"type":"string"}},"required":["type","expression","field","mapValue"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"config":{"$ref":"#/components/schemas/ConfigSpec"},"description":{"type":"string"},"label":{"type":"string"},"provisioned":{"type":"boolean"},"source_ds_ref":{"$ref":"#/components/schemas/DataSourceRef"},"target_ds_ref":{"$ref":"#/components/schemas/DataSourceRef"},"type":{"$ref":"#/components/schemas/CorrelationType"}},"required":["source_ds_ref","label","config","provisioned","type"],"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"}}`)
rawSchemaCorrelationv0alpha1 = []byte(`{"ConfigSpec":{"additionalProperties":false,"description":"there was a deprecated field here called type, we will need to move that for conversion and provisioning","properties":{"field":{"type":"string"},"target":{"$ref":"#/components/schemas/TargetSpec"},"transformations":{"items":{"$ref":"#/components/schemas/TransformationSpec"},"type":"array"}},"required":["field","target"],"type":"object"},"Correlation":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"CorrelationType":{"enum":["query","external"],"type":"string"},"DataSourceRef":{"additionalProperties":false,"properties":{"group":{"description":"same as pluginId","type":"string"},"name":{"description":"same as grafana uid","type":"string"}},"required":["group","name"],"type":"object"},"TargetSpec":{"additionalProperties":{"additionalProperties":{},"type":"object"},"type":"object"},"TransformationSpec":{"additionalProperties":false,"properties":{"expression":{"type":"string"},"field":{"type":"string"},"mapValue":{"type":"string"},"type":{"type":"string"}},"required":["type","expression","field","mapValue"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"config":{"$ref":"#/components/schemas/ConfigSpec"},"description":{"type":"string"},"label":{"type":"string"},"provisioned":{"type":"boolean"},"source_ds_ref":{"$ref":"#/components/schemas/DataSourceRef"},"target_ds_ref":{"$ref":"#/components/schemas/DataSourceRef"},"type":{"$ref":"#/components/schemas/CorrelationType"}},"required":["source_ds_ref","label","config","provisioned","type"],"type":"object"}}`)
versionSchemaCorrelationv0alpha1 app.VersionSchema
_ = json.Unmarshal(rawSchemaCorrelationv0alpha1, &versionSchemaCorrelationv0alpha1)
)
@ -28,6 +27,7 @@ var (
var appManifestData = app.ManifestData{
AppName: "correlation",
Group: "correlations.grafana.app",
PreferredVersion: "v0alpha1",
Versions: []app.ManifestVersion{
{
Name: "v0alpha1",

View File

@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana-app-sdk/simple"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -17,10 +18,12 @@ func New(cfg app.Config) (app.App, error) {
Name: "correlation",
KubeConfig: cfg.KubeConfig,
InformerConfig: simple.AppInformerConfig{
InformerOptions: operator.InformerOptions{
ErrorHandler: func(ctx context.Context, err error) {
logging.FromContext(ctx).Error("Informer processing error", "error", err)
},
},
},
ManagedKinds: []simple.AppManagedKind{
{
Kind: correlationsv0alpha1.CorrelationKind(),

View File

@ -2,7 +2,6 @@
* 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;
@ -45,5 +44,4 @@ export interface Correlation {
apiVersion: string;
metadata: Metadata;
spec: Spec;
status: Status;
}

View File

@ -1,30 +0,0 @@
// 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 => ({
});

View File

@ -4,8 +4,8 @@ go 1.24.6
require (
cuelang.org/go v0.11.1
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781
github.com/grafana/grafana-app-sdk v0.45.0
github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37
github.com/grafana/grafana-app-sdk v0.46.0
github.com/grafana/grafana-app-sdk/logging v0.45.0
github.com/grafana/grafana-plugin-sdk-go v0.279.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e
@ -31,7 +31,7 @@ require (
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/elazarl/goproxy v1.7.2 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
@ -51,7 +51,7 @@ require (
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c // indirect
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect
github.com/grafana/otel-profiling-go v0.5.1 // indirect
github.com/grafana/pyroscope-go/godeltaprof v0.1.8 // indirect
@ -134,7 +134,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect

View File

@ -36,8 +36,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY=
github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
@ -95,14 +95,14 @@ github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25d
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
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-20250926065801-df98203cff37 h1:qEwZ+7MbPjzRvTi31iT9w7NBhKIpKwZrFbYmOZLqkwA=
github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
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.45.0 h1:niFqYovxuw9vnUB9qoxEgmupqriG7Gns9ZGwB2uuOyE=
github.com/grafana/grafana-app-sdk v0.45.0/go.mod h1:1pYGEBrgG8i6pKmmsNXvtAr15jZ4iLtyHU4yj7T6XaI=
github.com/grafana/grafana-app-sdk v0.46.0 h1:gvzQvCQgZJ/73BfAcbDt/6TAMhnVikVPxZt/UwDl+oc=
github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc=
github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xuHKJjnO95/2pGOoA8w=
github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grafana/grafana-plugin-sdk-go v0.279.0 h1:/KCrsZkj9pEGwIGovqAz1A8rjI2A2YT+ZpvgfZN0LAA=
@ -386,8 +386,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -23,6 +23,7 @@ import (
var appManifestData = app.ManifestData{
AppName: "dashboard",
Group: "dashboard.grafana.app",
PreferredVersion: "v1beta1",
Versions: []app.ManifestVersion{
{
Name: "v0alpha1",

View File

@ -198,7 +198,7 @@ func sortPanelsByGridPos(dashboard map[string]interface{}) {
return
}
sort.Slice(panels, func(i, j int) bool {
sort.SliceStable(panels, func(i, j int) bool {
panelA := panels[i]
panelB := panels[j]
@ -831,7 +831,7 @@ func cleanupPanelList(panels []interface{}) {
// sortPanelsByGridPosition sorts panels by grid position (matches frontend sortPanelsByGridPos behavior)
func sortPanelsByGridPosition(panels []interface{}) {
sort.Slice(panels, func(i, j int) bool {
sort.SliceStable(panels, func(i, j int) bool {
panelA, okA := panels[i].(map[string]interface{})
panelB, okB := panels[j].(map[string]interface{})
if !okA || !okB {

View File

@ -49,10 +49,15 @@ func upgradeToGridLayout(dashboard map[string]interface{}) {
maxPanelID := getMaxPanelID(rows)
nextRowID := maxPanelID + 1
// Get existing panels
var finalPanels []interface{}
if existingPanels, ok := dashboard["panels"].([]interface{}); ok {
finalPanels = existingPanels
// Match frontend: dashboard.panels already exists with top-level panels
// The frontend's this.dashboard.panels is initialized in the constructor with existing panels
// Then upgradeToGridLayout adds more panels to it
// Initialize panels array - make a copy to avoid modifying the original
panels := []interface{}{}
if existingPanels, ok := dashboard["panels"].([]interface{}); ok && len(existingPanels) > 0 {
// Copy existing panels to preserve order
panels = append(panels, existingPanels...)
}
// Add special "row" panels if even one row is collapsed, repeated or has visible title (line 1028 in TS)
@ -72,7 +77,14 @@ func upgradeToGridLayout(dashboard map[string]interface{}) {
height := getRowHeight(row)
rowGridHeight := getGridHeight(height)
isCollapsed := GetBoolValue(row, "collapse")
// Check if collapse property exists and get its value
collapseValue, hasCollapseProperty := row["collapse"]
isCollapsed := false
if hasCollapseProperty {
if b, ok := collapseValue.(bool); ok {
isCollapsed = b
}
}
var rowPanel map[string]interface{}
@ -110,9 +122,9 @@ func upgradeToGridLayout(dashboard map[string]interface{}) {
},
}
// Set collapsed property only if the original row had a collapse property
// This matches the frontend behavior: rowPanel.collapsed = row.collapse
if _, hasCollapse := row["collapse"]; hasCollapse {
// Match frontend behavior: rowPanel.collapsed = row.collapse (line 1065 in TS)
// Only set collapsed property if the original row had a collapse property
if hasCollapseProperty {
rowPanel["collapsed"] = isCollapsed
}
nextRowID++
@ -128,23 +140,13 @@ func upgradeToGridLayout(dashboard map[string]interface{}) {
continue
}
// Set default span (line 1063 in TS)
span := GetFloatValue(panel, "span", defaultPanelSpan)
// Handle minSpan conversion (lines 1064-1066 in TS)
if minSpan, hasMinSpan := panel["minSpan"]; hasMinSpan {
if minSpanFloat, ok := ConvertToFloat(minSpan); ok && minSpanFloat > 0 {
panel["minSpan"] = int(math.Min(float64(gridColumnCount), (float64(gridColumnCount)/12.0)*minSpanFloat))
}
// Match frontend logic: panel.span = panel.span || DEFAULT_PANEL_SPAN (line 1082 in TS)
span := GetFloatValue(panel, "span", 0)
if span == 0 {
span = defaultPanelSpan
}
panelWidth := int(math.Floor(span * widthFactor))
panelHeight := rowGridHeight
if panelHeightValue, hasHeight := panel["height"]; hasHeight {
if h, ok := ConvertToFloat(panelHeightValue); ok {
panelHeight = getGridHeight(h)
}
}
panelWidth, panelHeight := calculatePanelDimensionsFromSpan(span, panel, widthFactor, rowGridHeight)
panelPos := rowArea.getPanelPosition(panelHeight, panelWidth)
yPos = rowArea.yPos
@ -161,21 +163,21 @@ func upgradeToGridLayout(dashboard map[string]interface{}) {
// Remove span (line 1080 in TS)
delete(panel, "span")
// Exact logic from lines 1082-1086 in TS
// Match frontend logic: lines 1101-1105 in TS
if rowPanel != nil && isCollapsed {
// Add to collapsed row's nested panels
// Add to collapsed row's nested panels (line 1102)
if rowPanelPanels, ok := rowPanel["panels"].([]interface{}); ok {
rowPanel["panels"] = append(rowPanelPanels, panel)
}
} else {
// Add directly to dashboard panels
finalPanels = append(finalPanels, panel)
// Add directly to panels array like frontend (line 1104)
panels = append(panels, panel)
}
}
// Add row panel after processing all panels (lines 1089-1091 in TS)
// Add row panel after regular panels from this row (lines 1108-1110 in TS)
if rowPanel != nil {
finalPanels = append(finalPanels, rowPanel)
panels = append(panels, rowPanel)
}
// Update yPos (lines 1093-1095 in TS)
@ -185,7 +187,7 @@ func upgradeToGridLayout(dashboard map[string]interface{}) {
}
// Update the dashboard
dashboard["panels"] = finalPanels
dashboard["panels"] = panels
delete(dashboard, "rows")
}
@ -315,3 +317,24 @@ func getGridHeight(height float64) int {
}
return int(math.Ceil(height / panelHeightStep))
}
func calculatePanelDimensionsFromSpan(span float64, panel map[string]interface{}, widthFactor float64, defaultHeight int) (int, int) {
// span should already be normalized by caller (line 1082 in DashboardMigrator.ts)
if minSpan, hasMinSpan := panel["minSpan"]; hasMinSpan {
if minSpanFloat, ok := ConvertToFloat(minSpan); ok && minSpanFloat > 0 {
panel["minSpan"] = int(math.Min(float64(gridColumnCount), (float64(gridColumnCount)/12.0)*minSpanFloat))
}
}
panelWidth := int(math.Floor(span * widthFactor))
panelHeight := defaultHeight
if panelHeightValue, hasHeight := panel["height"]; hasHeight {
if h, ok := ConvertToFloat(panelHeightValue); ok {
panelHeight = getGridHeight(h)
}
}
return panelWidth, panelHeight
}

View File

@ -1532,6 +1532,123 @@ func TestV16(t *testing.T) {
// rows field should be removed
},
},
{
name: "should handle span zero by defaulting to DEFAULT_PANEL_SPAN",
input: map[string]interface{}{
"schemaVersion": 15,
"rows": []interface{}{
map[string]interface{}{
"collapse": false,
"showTitle": true, // Need this to create row panel
"title": "Test Row",
"height": 250,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"span": 0, // This should be defaulted to 4 (DEFAULT_PANEL_SPAN)
},
map[string]interface{}{
"id": 2,
"type": "stat",
"span": 6, // Normal span value
},
},
},
},
},
expected: map[string]interface{}{
"schemaVersion": 16,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"gridPos": map[string]interface{}{
"x": 0,
"y": 1,
"w": 8, // span 0 -> DEFAULT_PANEL_SPAN (4) -> 4 * 2 = 8 width
"h": 7, // default height
},
},
map[string]interface{}{
"id": 2,
"type": "stat",
"gridPos": map[string]interface{}{
"x": 8, // After first panel
"y": 1,
"w": 12, // span 6 -> 6 * 2 = 12 width
"h": 7, // default height
},
},
// Row panel should be created because showTitle is true
map[string]interface{}{
"id": 3,
"type": "row",
"title": "Test Row",
"collapsed": false, // Set because input has "collapse": false
"repeat": "",
"panels": []interface{}{},
"gridPos": map[string]interface{}{
"x": 0,
"y": 0,
"w": 24,
"h": 7,
},
},
},
},
},
{
name: "should not set collapsed property when input row has no collapse property",
input: map[string]interface{}{
"schemaVersion": 15,
"rows": []interface{}{
map[string]interface{}{
// No "collapse" property in input
"showTitle": true,
"title": "Test Row",
"height": 250,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"span": 12,
},
},
},
},
},
expected: map[string]interface{}{
"schemaVersion": 16,
"panels": []interface{}{
map[string]interface{}{
"id": 1,
"type": "graph",
"gridPos": map[string]interface{}{
"x": 0,
"y": 1,
"w": 24, // span 12 -> 12 * 2 = 24 width
"h": 7, // default height
},
},
// Row panel should be created because showTitle is true
map[string]interface{}{
"id": 2,
"type": "row",
"title": "Test Row",
// No "collapsed" property because input had no "collapse" property
"repeat": "",
"panels": []interface{}{},
"gridPos": map[string]interface{}{
"x": 0,
"y": 0,
"w": 24,
"h": 7,
},
},
},
},
},
}
runMigrationTests(t, tests, schemaversion.V16)

View File

@ -0,0 +1,687 @@
{
"__requires": [
{
"id": "grafana",
"name": "Grafana",
"type": "grafana",
"version": "8.0.0"
}
],
"annotations": {
"list": []
},
"editable": false,
"gnetId": null,
"graphTooltip": 0,
"hideControls": false,
"links": [
{
"icon": "external link",
"targetBlank": true,
"title": "External Documentation",
"type": "link",
"url": "https://example.com/docs"
}
],
"panels": [
{
"gridPos": {
"h": 3,
"w": 24,
"x": 0,
"y": 0
},
"options": {
"content": "This dashboard demonstrates various monitoring components for application observability and performance metrics.\n",
"mode": "markdown"
},
"title": "Application Monitoring",
"type": "text"
}
],
"refresh": "10s",
"rows": [
{
"collapse": false,
"collapsed": false,
"height": "250px",
"panels": [
{
"gridPos": {
"h": 11,
"w": 24,
"x": 0,
"y": 5
},
"id": 6,
"options": {
"content": "This service handles background processing tasks for the application system. It manages various types of operations including data synchronization, resource management, and batch processing.\n\nSupported operation types:\n1. Sync: Synchronizes data between different systems\n2. Process: Handles batch data processing tasks\n3. Cleanup: Removes outdated or temporary resources\n4. Update: Applies configuration changes across services\n\nService dependencies:\n- Data API: For reading and writing application data\n- Configuration Service: For managing system settings\n- Queue Service: For handling task scheduling\n- Storage Service: For persistent data management\n- Auth Service: For authentication and authorization\n- Metrics Service: For collecting operational statistics\n",
"mode": "markdown"
},
"span": 0,
"title": "Service Overview",
"type": "text"
},
{
"gridPos": {
"h": 3,
"w": 24,
"x": 0,
"y": 16
},
"id": 7,
"options": {
"content": "Error monitoring helps identify issues in the system. This section displays error logs and success rates for operations.",
"mode": "markdown"
},
"span": 0,
"title": "Error Monitoring",
"type": "text"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": 0
},
{
"color": "yellow",
"value": 0.95
},
{
"color": "green",
"value": 1
}
]
},
"unit": "percentunit"
}
},
"gridPos": {
"h": 9,
"w": 3,
"x": 0,
"y": 19
},
"id": 8,
"span": 0,
"targets": [
{
"expr": "sum by (action) (app_jobs_processed_total{outcome=\"success\", cluster=\"$cluster\", namespace=\"default\"})\n/\nsum by (action) (app_jobs_processed_total{cluster=\"$cluster\", namespace=\"default\"})\n",
"legendFormat": "{{action}}"
}
],
"title": "Job Success Rate",
"type": "stat"
},
{
"datasource": {
"type": "loki",
"uid": "${loki}"
},
"gridPos": {
"h": 9,
"w": 10,
"x": 3,
"y": 19
},
"id": 9,
"options": {
"enableLogDetails": true,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": true
},
"span": 0,
"targets": [
{
"expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt | level=\"error\""
}
],
"title": "Errors",
"type": "logs"
},
{
"datasource": {
"type": "loki",
"uid": "${loki}"
},
"gridPos": {
"h": 9,
"w": 11,
"x": 13,
"y": 19
},
"id": 10,
"options": {
"enableLogDetails": true,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": true
},
"span": 0,
"targets": [
{
"expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt"
}
],
"title": "All",
"type": "logs"
},
{
"gridPos": {
"h": 3,
"w": 24,
"x": 0,
"y": 28
},
"id": 11,
"options": {
"content": "Performance monitoring examines factors that affect system response times, including operation duration, queue lengths, and processing delays. This section provides metrics and traces for performance analysis.\n",
"mode": "markdown"
},
"span": 0,
"title": "Performance Analysis",
"type": "text"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Number of concurrent processing threads available for handling operations",
"gridPos": {
"h": 6,
"w": 5,
"x": 0,
"y": 31
},
"id": 12,
"span": 0,
"targets": [
{
"expr": "max(app_worker_threads_active{cluster=\"$cluster\", namespace=\"default\"})",
"instant": true
}
],
"title": "Concurrent Job Drivers",
"type": "stat"
},
{
"datasource": {
"type": "tempo",
"uid": "${tempo}"
},
"gridPos": {
"h": 6,
"w": 19,
"x": 5,
"y": 31
},
"id": 13,
"span": 0,
"targets": [
{
"filters": [
{
"id": "span-name",
"operator": "=",
"scope": "span",
"tag": "name",
"value": [
"provisioning.sync.process"
]
},
{
"id": "k8s-cluster-name",
"operator": "=",
"scope": "resource",
"tag": "k8s.cluster.name",
"value": [
"$cluster"
]
}
],
"query": "{name=\"app.operation.process\"}",
"queryType": "traceqlSearch"
}
],
"title": "Recent Operation Traces",
"type": "table"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed",
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "yellow",
"value": 2
},
{
"color": "red",
"value": 5
}
]
},
"unit": "s"
}
},
"gridPos": {
"h": 10,
"w": 8,
"x": 0,
"y": 55
},
"id": 14,
"span": 0,
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) > 0",
"legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}",
"refId": "B"
},
{
"expr": "histogram_quantile(0.9, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) > 0",
"legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}",
"refId": "C"
},
{
"expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) > 0",
"legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}",
"refId": "D"
},
{
"expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) > 0",
"legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}",
"refId": "E"
}
],
"timeFrom": "7d",
"title": "7d avg of job durations",
"transformations": [
{
"id": "reduce",
"options": {
"mode": "seriesToRows",
"reducers": [
"mean"
]
}
},
{
"id": "seriesToRows"
},
{
"id": "organize",
"options": {
"renameByName": {
"Field": "Type",
"Mean": "Avg Duration",
"Metric": "Legend",
"Value": "Duration"
}
}
}
],
"type": "table"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed",
"gridPos": {
"h": 10,
"w": 16,
"x": 8,
"y": 55
},
"id": 15,
"span": 0,
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}",
"refId": "B"
},
{
"expr": "histogram_quantile(0.95, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}",
"refId": "C"
},
{
"expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}",
"refId": "D"
},
{
"expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}",
"refId": "E"
}
],
"title": "Job Duration",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Total number of jobs waiting to be processed",
"gridPos": {
"h": 5,
"w": 4,
"x": 0,
"y": 65
},
"id": 16,
"span": 0,
"targets": [
{
"expr": "clamp_min(sum(app_operation_queue_size{cluster=\"$cluster\", namespace=\"default\"}), 0)",
"legendFormat": "Queue size"
}
],
"title": "Queue Size",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"fieldConfig": {
"defaults": {
"unit": "s"
}
},
"gridPos": {
"h": 5,
"w": 4,
"x": 4,
"y": 65
},
"id": 17,
"span": 0,
"targets": [
{
"expr": "avg(histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le)))",
"legendFormat": "Queue size"
}
],
"timeFrom": "7d",
"title": "7d avg Queue Wait Time",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "How long a job is in the queue before being picked up",
"gridPos": {
"h": 5,
"w": 16,
"x": 8,
"y": 65
},
"id": 18,
"span": 0,
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.99",
"refId": "B"
},
{
"expr": "histogram_quantile(0.95, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.95",
"refId": "C"
},
{
"expr": "histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.5",
"refId": "D"
},
{
"expr": "histogram_quantile(0.1, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.1",
"refId": "E"
}
],
"title": "Queue Wait Time",
"type": "timeseries"
},
{
"gridPos": {
"h": 3,
"w": 24,
"x": 0,
"y": 52
},
"id": 19,
"options": {
"content": "Resource utilization monitoring for application containers",
"mode": "markdown"
},
"span": 0,
"title": "Resource Monitoring",
"type": "text"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"gridPos": {
"h": 9,
"w": 7,
"x": 0,
"y": 55
},
"id": 20,
"span": 0,
"targets": [
{
"expr": "count by (cluster, channel)(label_replace(label_replace(kube_pod_container_info{namespace=\"default\", container=\"app-worker\", pod=~\"app-worker.*\", cluster=~\"$cluster\"}, \"version\", \"$1\", \"image\", \".+:(.+)\"), \"channel\", \"$1\", \"container\", \".+-(.+)\"))",
"legendFormat": "{{cluster}}"
}
],
"title": "Running Pod(s)",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"gridPos": {
"h": 9,
"w": 8,
"x": 7,
"y": 55
},
"id": 21,
"span": 0,
"targets": [
{
"expr": "max(kube_pod_container_resource_requests{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})",
"legendFormat": "Memory Request"
},
{
"expr": "max(kube_pod_container_resource_limits{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})",
"legendFormat": "Memory Limit"
},
{
"expr": "max(container_memory_usage_bytes{namespace=\"default\",cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"}) by (pod)",
"legendFormat": "Container usage {{pod}}"
}
],
"title": "Memory Utilization",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"gridPos": {
"h": 9,
"w": 9,
"x": 15,
"y": 55
},
"id": 22,
"span": 0,
"targets": [
{
"expr": "sum(irate(container_cpu_usage_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container, cpu)",
"legendFormat": "Usage {{pod}}"
},
{
"expr": "sum(irate(container_cpu_cfs_throttled_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container)",
"legendFormat": "Throttling {{pod}}"
},
{
"expr": "max(kube_pod_container_resource_limits{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})",
"legendFormat": "CPU limit"
},
{
"expr": "max(kube_pod_container_resource_requests{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})",
"legendFormat": "CPU request"
}
],
"title": "CPU Utilization",
"type": "timeseries"
}
],
"repeat": null,
"repeatIteration": null,
"repeatRowId": null,
"showTitle": true,
"title": "Application Service",
"titleSize": "h6"
}
],
"schemaVersion": 15,
"style": "dark",
"tags": [
"as-code"
],
"templating": {
"list": [
{
"current": {
"value": "prometheus-datasource"
},
"hide": 0,
"label": "Data source",
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"current": {
"value": "prometheus-datasource"
},
"name": "prom",
"query": "prometheus",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"current": {
"value": "loki-datasource"
},
"name": "loki",
"query": "loki",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"current": {
"text": "tempo-datasource",
"value": "tempo-datasource"
},
"name": "tempo",
"query": "tempo",
"refresh": 1,
"regex": ".*tempo.*",
"type": "datasource"
},
{
"current": {
"text": "demo-cluster",
"value": "demo-cluster"
},
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"name": "cluster",
"query": "label_values(app_worker_threads_active,cluster)",
"refresh": 1,
"type": "query"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "utc",
"title": "Span Zero Demo Dashboard",
"uid": "span-zero-demo-dashboard",
"version": 0
}

View File

@ -0,0 +1,881 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"type": "dashboard"
}
]
},
"editable": false,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [
{
"icon": "external link",
"targetBlank": true,
"title": "External Documentation",
"type": "link",
"url": "https://example.com/docs"
}
],
"panels": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 3,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"content": "This dashboard demonstrates various monitoring components for application observability and performance metrics.\n",
"mode": "markdown"
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Application Monitoring",
"type": "text"
},
{
"collapsed": false,
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 7,
"w": 24,
"x": 0,
"y": 0
},
"id": 23,
"panels": [],
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Application Service",
"type": "row"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 1
},
"id": 6,
"options": {
"content": "This service handles background processing tasks for the application system. It manages various types of operations including data synchronization, resource management, and batch processing.\n\nSupported operation types:\n1. Sync: Synchronizes data between different systems\n2. Process: Handles batch data processing tasks\n3. Cleanup: Removes outdated or temporary resources\n4. Update: Applies configuration changes across services\n\nService dependencies:\n- Data API: For reading and writing application data\n- Configuration Service: For managing system settings\n- Queue Service: For handling task scheduling\n- Storage Service: For persistent data management\n- Auth Service: For authentication and authorization\n- Metrics Service: For collecting operational statistics\n",
"mode": "markdown"
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Service Overview",
"type": "text"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 1
},
"id": 7,
"options": {
"content": "Error monitoring helps identify issues in the system. This section displays error logs and success rates for operations.",
"mode": "markdown"
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Error Monitoring",
"type": "text"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": 0
},
{
"color": "yellow",
"value": 0.95
},
{
"color": "green",
"value": 1
}
]
},
"unit": "percentunit"
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 1
},
"id": 8,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "sum by (action) (app_jobs_processed_total{outcome=\"success\", cluster=\"$cluster\", namespace=\"default\"})\n/\nsum by (action) (app_jobs_processed_total{cluster=\"$cluster\", namespace=\"default\"})\n",
"legendFormat": "{{action}}",
"refId": "A"
}
],
"title": "Job Success Rate",
"type": "stat"
},
{
"datasource": {
"type": "loki",
"uid": "${loki}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 8
},
"id": 9,
"options": {
"enableLogDetails": true,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": true
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "${loki}"
},
"expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt | level=\"error\"",
"refId": "A"
}
],
"title": "Errors",
"type": "logs"
},
{
"datasource": {
"type": "loki",
"uid": "${loki}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 8
},
"id": 10,
"options": {
"enableLogDetails": true,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": true
},
"targets": [
{
"datasource": {
"type": "loki",
"uid": "${loki}"
},
"expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt",
"refId": "A"
}
],
"title": "All",
"type": "logs"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 8
},
"id": 11,
"options": {
"content": "Performance monitoring examines factors that affect system response times, including operation duration, queue lengths, and processing delays. This section provides metrics and traces for performance analysis.\n",
"mode": "markdown"
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Performance Analysis",
"type": "text"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Number of concurrent processing threads available for handling operations",
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 15
},
"id": 12,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "max(app_worker_threads_active{cluster=\"$cluster\", namespace=\"default\"})",
"instant": true,
"refId": "A"
}
],
"title": "Concurrent Job Drivers",
"type": "stat"
},
{
"datasource": {
"type": "tempo",
"uid": "${tempo}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 15
},
"id": 13,
"targets": [
{
"datasource": {
"type": "tempo",
"uid": "${tempo}"
},
"filters": [
{
"id": "span-name",
"operator": "=",
"scope": "span",
"tag": "name",
"value": [
"provisioning.sync.process"
]
},
{
"id": "k8s-cluster-name",
"operator": "=",
"scope": "resource",
"tag": "k8s.cluster.name",
"value": [
"$cluster"
]
}
],
"query": "{name=\"app.operation.process\"}",
"queryType": "traceqlSearch",
"refId": "A"
}
],
"title": "Recent Operation Traces",
"type": "table"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed",
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "yellow",
"value": 2
},
{
"color": "red",
"value": 5
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 15
},
"id": 14,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0",
"legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.9, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0",
"legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}",
"refId": "C"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0",
"legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}",
"refId": "D"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0",
"legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}",
"refId": "E"
}
],
"timeFrom": "7d",
"title": "7d avg of job durations",
"transformations": [
{
"id": "reduce",
"options": {
"mode": "seriesToRows",
"reducers": [
"mean"
]
}
},
{
"id": "seriesToRows"
},
{
"id": "organize",
"options": {
"renameByName": {
"Field": "Type",
"Mean": "Avg Duration",
"Metric": "Legend",
"Value": "Duration"
}
}
}
],
"type": "table"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed",
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 22
},
"id": 15,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.95, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}",
"refId": "C"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}",
"refId": "D"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}",
"refId": "E"
}
],
"title": "Job Duration",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Total number of jobs waiting to be processed",
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 22
},
"id": 16,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "clamp_min(sum(app_operation_queue_size{cluster=\"$cluster\", namespace=\"default\"}), 0)",
"legendFormat": "Queue size",
"refId": "A"
}
],
"title": "Queue Size",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 22
},
"id": 17,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "avg(histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le)))",
"legendFormat": "Queue size",
"refId": "A"
}
],
"timeFrom": "7d",
"title": "7d avg Queue Wait Time",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "How long a job is in the queue before being picked up",
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 29
},
"id": 18,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.99, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.99",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.95, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.95",
"refId": "C"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.5",
"refId": "D"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "histogram_quantile(0.1, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.1",
"refId": "E"
}
],
"title": "Queue Wait Time",
"type": "timeseries"
},
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 29
},
"id": 19,
"options": {
"content": "Resource utilization monitoring for application containers",
"mode": "markdown"
},
"targets": [
{
"datasource": {
"apiVersion": "v1",
"type": "prometheus",
"uid": "default-ds-uid"
},
"refId": "A"
}
],
"title": "Resource Monitoring",
"type": "text"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 29
},
"id": 20,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "count by (cluster, channel)(label_replace(label_replace(kube_pod_container_info{namespace=\"default\", container=\"app-worker\", pod=~\"app-worker.*\", cluster=~\"$cluster\"}, \"version\", \"$1\", \"image\", \".+:(.+)\"), \"channel\", \"$1\", \"container\", \".+-(.+)\"))",
"legendFormat": "{{cluster}}",
"refId": "A"
}
],
"title": "Running Pod(s)",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 36
},
"id": 21,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "max(kube_pod_container_resource_requests{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})",
"legendFormat": "Memory Request",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "max(kube_pod_container_resource_limits{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})",
"legendFormat": "Memory Limit",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "max(container_memory_usage_bytes{namespace=\"default\",cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"}) by (pod)",
"legendFormat": "Container usage {{pod}}",
"refId": "C"
}
],
"title": "Memory Utilization",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 36
},
"id": 22,
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "sum(irate(container_cpu_usage_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container, cpu)",
"legendFormat": "Usage {{pod}}",
"refId": "A"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "sum(irate(container_cpu_cfs_throttled_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container)",
"legendFormat": "Throttling {{pod}}",
"refId": "B"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "max(kube_pod_container_resource_limits{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})",
"legendFormat": "CPU limit",
"refId": "C"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"expr": "max(kube_pod_container_resource_requests{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})",
"legendFormat": "CPU request",
"refId": "D"
}
],
"title": "CPU Utilization",
"type": "timeseries"
}
],
"refresh": "10s",
"schemaVersion": 42,
"tags": [
"as-code"
],
"templating": {
"list": [
{
"current": {
"value": "prometheus-datasource"
},
"hide": 0,
"label": "Data source",
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"current": {
"value": "prometheus-datasource"
},
"name": "prom",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"current": {
"value": "loki-datasource"
},
"name": "loki",
"options": [],
"query": "loki",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"current": {
"text": "tempo-datasource",
"value": "tempo-datasource"
},
"name": "tempo",
"options": [],
"query": "tempo",
"refresh": 1,
"regex": ".*tempo.*",
"type": "datasource"
},
{
"current": {
"text": "demo-cluster",
"value": "demo-cluster"
},
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"name": "cluster",
"options": [],
"query": "label_values(app_worker_threads_active,cluster)",
"refresh": 1,
"type": "query"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
]
},
"timezone": "utc",
"title": "Span Zero Demo Dashboard",
"uid": "span-zero-demo-dashboard",
"weekStart": ""
}

View File

@ -0,0 +1,694 @@
{
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {
"type": "grafana",
"uid": "-- Grafana --"
},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations \u0026 Alerts",
"type": "dashboard"
}
]
},
"editable": false,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"links": [
{
"icon": "external link",
"targetBlank": true,
"title": "External Documentation",
"type": "link",
"url": "https://example.com/docs"
}
],
"panels": [
{
"gridPos": {
"h": 3,
"w": 24,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"content": "This dashboard demonstrates various monitoring components for application observability and performance metrics.\n",
"mode": "markdown"
},
"title": "Application Monitoring",
"type": "text"
},
{
"collapsed": false,
"gridPos": {
"h": 7,
"w": 24,
"x": 0,
"y": 0
},
"id": 23,
"panels": [],
"title": "Application Service",
"type": "row"
},
{
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 1
},
"id": 6,
"options": {
"content": "This service handles background processing tasks for the application system. It manages various types of operations including data synchronization, resource management, and batch processing.\n\nSupported operation types:\n1. Sync: Synchronizes data between different systems\n2. Process: Handles batch data processing tasks\n3. Cleanup: Removes outdated or temporary resources\n4. Update: Applies configuration changes across services\n\nService dependencies:\n- Data API: For reading and writing application data\n- Configuration Service: For managing system settings\n- Queue Service: For handling task scheduling\n- Storage Service: For persistent data management\n- Auth Service: For authentication and authorization\n- Metrics Service: For collecting operational statistics\n",
"mode": "markdown"
},
"title": "Service Overview",
"type": "text"
},
{
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 1
},
"id": 7,
"options": {
"content": "Error monitoring helps identify issues in the system. This section displays error logs and success rates for operations.",
"mode": "markdown"
},
"title": "Error Monitoring",
"type": "text"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": 0
},
{
"color": "yellow",
"value": 0.95
},
{
"color": "green",
"value": 1
}
]
},
"unit": "percentunit"
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 1
},
"id": 8,
"targets": [
{
"expr": "sum by (action) (app_jobs_processed_total{outcome=\"success\", cluster=\"$cluster\", namespace=\"default\"})\n/\nsum by (action) (app_jobs_processed_total{cluster=\"$cluster\", namespace=\"default\"})\n",
"legendFormat": "{{action}}",
"refId": "A"
}
],
"title": "Job Success Rate",
"type": "stat"
},
{
"datasource": {
"type": "loki",
"uid": "${loki}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 8
},
"id": 9,
"options": {
"enableLogDetails": true,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": true
},
"targets": [
{
"expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt | level=\"error\"",
"refId": "A"
}
],
"title": "Errors",
"type": "logs"
},
{
"datasource": {
"type": "loki",
"uid": "${loki}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 8
},
"id": 10,
"options": {
"enableLogDetails": true,
"showTime": false,
"sortOrder": "Descending",
"wrapLogMessage": true
},
"targets": [
{
"expr": "{namespace=\"default\", cluster=\"$cluster\", job=\"app-service\"} | logfmt",
"refId": "A"
}
],
"title": "All",
"type": "logs"
},
{
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 8
},
"id": 11,
"options": {
"content": "Performance monitoring examines factors that affect system response times, including operation duration, queue lengths, and processing delays. This section provides metrics and traces for performance analysis.\n",
"mode": "markdown"
},
"title": "Performance Analysis",
"type": "text"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Number of concurrent processing threads available for handling operations",
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 15
},
"id": 12,
"targets": [
{
"expr": "max(app_worker_threads_active{cluster=\"$cluster\", namespace=\"default\"})",
"instant": true,
"refId": "A"
}
],
"title": "Concurrent Job Drivers",
"type": "stat"
},
{
"datasource": {
"type": "tempo",
"uid": "${tempo}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 15
},
"id": 13,
"targets": [
{
"filters": [
{
"id": "span-name",
"operator": "=",
"scope": "span",
"tag": "name",
"value": [
"provisioning.sync.process"
]
},
{
"id": "k8s-cluster-name",
"operator": "=",
"scope": "resource",
"tag": "k8s.cluster.name",
"value": [
"$cluster"
]
}
],
"query": "{name=\"app.operation.process\"}",
"queryType": "traceqlSearch",
"refId": "A"
}
],
"title": "Recent Operation Traces",
"type": "table"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed",
"fieldConfig": {
"defaults": {
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": 0
},
{
"color": "yellow",
"value": 2
},
{
"color": "red",
"value": 5
}
]
},
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 15
},
"id": 14,
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0",
"legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}",
"refId": "B"
},
{
"expr": "histogram_quantile(0.9, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0",
"legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}",
"refId": "C"
},
{
"expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0",
"legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}",
"refId": "D"
},
{
"expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le, resources_changed_bucket, action)) and on(resources_changed_bucket, action) sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (resources_changed_bucket, action) \u003e 0",
"legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}",
"refId": "E"
}
],
"timeFrom": "7d",
"title": "7d avg of job durations",
"transformations": [
{
"id": "reduce",
"options": {
"mode": "seriesToRows",
"reducers": [
"mean"
]
}
},
{
"id": "seriesToRows"
},
{
"id": "organize",
"options": {
"renameByName": {
"Field": "Type",
"Mean": "Avg Duration",
"Metric": "Legend",
"Value": "Duration"
}
}
}
],
"type": "table"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Histogram showing p99, p95, p50, and p10 percentiles for job processing duration based on number of resources changed",
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 22
},
"id": 15,
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.99 - size {{resources_changed_bucket}}",
"refId": "B"
},
{
"expr": "histogram_quantile(0.95, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.95 - size {{resources_changed_bucket}}",
"refId": "C"
},
{
"expr": "histogram_quantile(0.5, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.5 - size {{resources_changed_bucket}}",
"refId": "D"
},
{
"expr": "histogram_quantile(0.1, sum(rate(app_operation_duration_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[5m])) by (le, resources_changed_bucket, action))",
"legendFormat": "{{action}} q0.1 - size {{resources_changed_bucket}}",
"refId": "E"
}
],
"title": "Job Duration",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "Total number of jobs waiting to be processed",
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 22
},
"id": 16,
"targets": [
{
"expr": "clamp_min(sum(app_operation_queue_size{cluster=\"$cluster\", namespace=\"default\"}), 0)",
"legendFormat": "Queue size",
"refId": "A"
}
],
"title": "Queue Size",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 22
},
"id": 17,
"targets": [
{
"expr": "avg(histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[7d])) by (le)))",
"legendFormat": "Queue size",
"refId": "A"
}
],
"timeFrom": "7d",
"title": "7d avg Queue Wait Time",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"description": "How long a job is in the queue before being picked up",
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 29
},
"id": 18,
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.99",
"refId": "B"
},
{
"expr": "histogram_quantile(0.95, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.95",
"refId": "C"
},
{
"expr": "histogram_quantile(0.5, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.5",
"refId": "D"
},
{
"expr": "histogram_quantile(0.1, sum(rate(app_operation_queue_wait_seconds_bucket{cluster=\"$cluster\", namespace=\"default\"}[$__rate_interval])) by (le))",
"legendFormat": "q0.1",
"refId": "E"
}
],
"title": "Queue Wait Time",
"type": "timeseries"
},
{
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 29
},
"id": 19,
"options": {
"content": "Resource utilization monitoring for application containers",
"mode": "markdown"
},
"title": "Resource Monitoring",
"type": "text"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 16,
"y": 29
},
"id": 20,
"targets": [
{
"expr": "count by (cluster, channel)(label_replace(label_replace(kube_pod_container_info{namespace=\"default\", container=\"app-worker\", pod=~\"app-worker.*\", cluster=~\"$cluster\"}, \"version\", \"$1\", \"image\", \".+:(.+)\"), \"channel\", \"$1\", \"container\", \".+-(.+)\"))",
"legendFormat": "{{cluster}}",
"refId": "A"
}
],
"title": "Running Pod(s)",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 0,
"y": 36
},
"id": 21,
"targets": [
{
"expr": "max(kube_pod_container_resource_requests{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})",
"legendFormat": "Memory Request",
"refId": "A"
},
{
"expr": "max(kube_pod_container_resource_limits{namespace=\"default\", resource=\"memory\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"})",
"legendFormat": "Memory Limit",
"refId": "B"
},
{
"expr": "max(container_memory_usage_bytes{namespace=\"default\",cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker.*\"}) by (pod)",
"legendFormat": "Container usage {{pod}}",
"refId": "C"
}
],
"title": "Memory Utilization",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"gridPos": {
"h": 7,
"w": 8,
"x": 8,
"y": 36
},
"id": 22,
"targets": [
{
"expr": "sum(irate(container_cpu_usage_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container, cpu)",
"legendFormat": "Usage {{pod}}",
"refId": "A"
},
{
"expr": "sum(irate(container_cpu_cfs_throttled_seconds_total{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\"}[$__rate_interval])) by (pod, container)",
"legendFormat": "Throttling {{pod}}",
"refId": "B"
},
{
"expr": "max(kube_pod_container_resource_limits{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})",
"legendFormat": "CPU limit",
"refId": "C"
},
{
"expr": "max(kube_pod_container_resource_requests{namespace=\"default\", cluster=~\"$cluster\", container=\"app-worker\", pod=~\"app-worker-.*\", resource=\"cpu\"})",
"legendFormat": "CPU request",
"refId": "D"
}
],
"title": "CPU Utilization",
"type": "timeseries"
}
],
"refresh": "10s",
"schemaVersion": 16,
"tags": [
"as-code"
],
"templating": {
"list": [
{
"current": {
"value": "prometheus-datasource"
},
"hide": 0,
"label": "Data source",
"name": "datasource",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"current": {
"value": "prometheus-datasource"
},
"name": "prom",
"options": [],
"query": "prometheus",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"current": {
"value": "loki-datasource"
},
"name": "loki",
"options": [],
"query": "loki",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"current": {
"text": "tempo-datasource",
"value": "tempo-datasource"
},
"name": "tempo",
"options": [],
"query": "tempo",
"refresh": 1,
"regex": ".*tempo.*",
"type": "datasource"
},
{
"current": {
"text": "demo-cluster",
"value": "demo-cluster"
},
"datasource": {
"type": "prometheus",
"uid": "${prom}"
},
"name": "cluster",
"options": [],
"query": "label_values(app_worker_threads_active,cluster)",
"refresh": 1,
"type": "query"
}
]
},
"time": {
"from": "now-6h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "utc",
"title": "Span Zero Demo Dashboard",
"uid": "span-zero-demo-dashboard",
"weekStart": ""
}

View File

@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/folder
go 1.24.6
require (
github.com/grafana/grafana-app-sdk v0.45.0
github.com/grafana/grafana-app-sdk v0.46.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e
k8s.io/apimachinery v0.34.1
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
@ -13,7 +13,7 @@ require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
@ -55,7 +55,7 @@ require (
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.13.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/client-go v0.34.1 // indirect

View File

@ -6,8 +6,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ=
@ -31,8 +31,8 @@ github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/grafana-app-sdk v0.45.0 h1:niFqYovxuw9vnUB9qoxEgmupqriG7Gns9ZGwB2uuOyE=
github.com/grafana/grafana-app-sdk v0.45.0/go.mod h1:1pYGEBrgG8i6pKmmsNXvtAr15jZ4iLtyHU4yj7T6XaI=
github.com/grafana/grafana-app-sdk v0.46.0 h1:gvzQvCQgZJ/73BfAcbDt/6TAMhnVikVPxZt/UwDl+oc=
github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc=
github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xuHKJjnO95/2pGOoA8w=
github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250514132646-acbc7b54ed9e h1:BTKk7LHuG1kmAkucwTA7DuMbKpKvJTKrGdBmUNO4dfQ=
@ -152,8 +152,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -20,6 +20,7 @@ import (
var appManifestData = app.ManifestData{
AppName: "folder",
Group: "folder.grafana.app",
PreferredVersion: "v1beta1",
Versions: []app.ManifestVersion{
{
Name: "v1beta1",

View File

@ -8,6 +8,7 @@ generate: install-app-sdk update-app-sdk ## Run Grafana App SDK code generation
--grouping=group \
--defencoding=none \
--noschemasinmanifest \
--genoperatorstate=false \
--postprocess
.PHONY: deps

View File

@ -3,6 +3,17 @@
# https://docs.tilt.dev/api.html#api.version_settings
version_settings(constraint='>=0.22.2')
custom_build(
'iam-folder-reconciler',
command='docker buildx build --tag $EXPECTED_REF -f ./apps/iam/local/Dockerfile .',
deps=[
'apps/iam',
'pkg',
],
disable_push=True,
dir='../..',
)
k8s_yaml([filename for filename in listdir('local/yamls') if filename.lower().endswith(('.yaml', '.yml'))])
# Port forward Grafana to localhost:3000

View File

@ -1,16 +0,0 @@
FROM golang:1.24-alpine AS builder
WORKDIR /build
COPY go.mod go.sum ./
COPY vendor* ./vendor
RUN test -f vendor/modules.txt || go mod download
COPY cmd cmd
COPY pkg pkg
RUN go build -o "target/operator" cmd/operator/*.go
FROM alpine AS runtime
COPY --from=builder /build/target/operator /usr/bin/operator
ENTRYPOINT ["/usr/bin/operator"]

View File

@ -1,31 +0,0 @@
package main
import (
"fmt"
"net/http"
utilnet "k8s.io/apimachinery/pkg/util/net"
"github.com/grafana/authlib/authn"
)
type authRoundTripper struct {
tokenExchangeClient *authn.TokenExchangeClient
transport http.RoundTripper
}
func (t *authRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
tokenResponse, err := t.tokenExchangeClient.Exchange(req.Context(), authn.TokenExchangeRequest{
Audiences: []string{"folder.grafana.app"},
Namespace: "*",
})
if err != nil {
return nil, fmt.Errorf("failed to exchange token: %w", err)
}
// clone the request as RTs are not expected to mutate the passed request
req = utilnet.CloneRequest(req)
req.Header.Set("X-Access-Token", "Bearer "+tokenResponse.Token)
return t.transport.RoundTrip(req)
}

View File

@ -1,131 +0,0 @@
package main
import (
"fmt"
"os"
"strconv"
"strings"
"github.com/grafana/grafana-app-sdk/plugin/kubeconfig"
"github.com/grafana/grafana-app-sdk/simple"
"github.com/grafana/grafana/pkg/services/authz"
)
const (
ConnTypeGRPC = "grpc"
ConnTypeHTTP = "http"
)
type Config struct {
OTelConfig simple.OpenTelemetryConfig
WebhookServer WebhookServerConfig
KubeConfig *kubeconfig.NamespacedConfig
ZanzanaClient authz.ZanzanaClientConfig
FolderReconciler FolderReconcilerConfig
}
type WebhookServerConfig struct {
Port int
TLSCertPath string
TLSKeyPath string
}
type FolderReconcilerConfig struct {
Namespace string
MaxConcurrentWorkers uint64
}
func LoadConfigFromEnv() (*Config, error) {
cfg := Config{}
cfg.OTelConfig.ServiceName = os.Getenv("OTEL_SERVICE_NAME")
switch strings.ToLower(os.Getenv("OTEL_CONN_TYPE")) {
case ConnTypeGRPC:
cfg.OTelConfig.ConnType = ConnTypeGRPC
case ConnTypeHTTP:
cfg.OTelConfig.ConnType = ConnTypeHTTP
case "":
// Default
cfg.OTelConfig.ConnType = ConnTypeHTTP
default:
return nil, fmt.Errorf("unknown OTEL_CONN_TYPE '%s'", os.Getenv("OTEL_CONN_TYPE"))
}
cfg.OTelConfig.Host = os.Getenv("OTEL_HOST")
portStr := os.Getenv("OTEL_PORT")
if portStr == "" {
if cfg.OTelConfig.ConnType == ConnTypeGRPC {
// Default OTel GRPC port
cfg.OTelConfig.Port = 4317
} else {
// Default OTel HTTP port
cfg.OTelConfig.Port = 4318
}
} else {
var err error
cfg.OTelConfig.Port, err = strconv.Atoi(portStr)
if err != nil {
return nil, fmt.Errorf("invalid OTEL_PORT '%s': %w", portStr, err)
}
}
whPortStr := os.Getenv("WEBHOOK_PORT")
if whPortStr == "" {
cfg.WebhookServer.Port = 8443
} else {
var err error
cfg.WebhookServer.Port, err = strconv.Atoi(whPortStr)
if err != nil {
return nil, fmt.Errorf("invalid WEBHOOK_PORT '%s': %w", whPortStr, err)
}
}
cfg.WebhookServer.TLSCertPath = os.Getenv("WEBHOOK_CERT_PATH")
cfg.WebhookServer.TLSKeyPath = os.Getenv("WEBHOOK_KEY_PATH")
// Load the kube config
kubeConfigFile := os.Getenv("KUBE_CONFIG_FILE")
if kubeConfigFile != "" {
kubeConfig, err := LoadKubeConfigFromFile(kubeConfigFile)
if err != nil {
return nil, fmt.Errorf("unable to load kubernetes configuration from file '%s': %w", kubeConfigFile, err)
}
cfg.KubeConfig = kubeConfig
} else if folderAppURL := os.Getenv("FOLDER_APP_URL"); folderAppURL != "" {
exchangeUrl := os.Getenv("TOKEN_EXCHANGE_URL")
authToken := os.Getenv("AUTH_TOKEN")
namespace := os.Getenv("FOLDER_APP_NAMESPACE")
if exchangeUrl == "" || authToken == "" {
return nil, fmt.Errorf("TOKEN_EXCHANGE_URL and AUTH_TOKEN must be set when FOLDER_APP_URL is set")
}
kubeConfig, err := LoadKubeConfigFromFolderAppURL(folderAppURL, exchangeUrl, authToken, namespace)
if err != nil {
return nil, fmt.Errorf("unable to load kubernetes configuration from folder app URL '%s': %w", folderAppURL, err)
}
cfg.KubeConfig = kubeConfig
} else {
kubeConfig, err := LoadInClusterConfig()
if err != nil {
return nil, fmt.Errorf("unable to load in-cluster kubernetes configuration: %w", err)
}
cfg.KubeConfig = kubeConfig
}
cfg.ZanzanaClient.URL = os.Getenv("ZANZANA_ADDR")
cfg.ZanzanaClient.Token = os.Getenv("ZANZANA_TOKEN")
cfg.ZanzanaClient.TokenExchangeURL = os.Getenv("TOKEN_EXCHANGE_URL")
cfg.ZanzanaClient.ServerCertFile = os.Getenv("ZANZANA_SERVER_CERT_FILE")
cfg.FolderReconciler.Namespace = os.Getenv("FOLDER_RECONCILER_NAMESPACE")
maxConcurrentWorkersStr := os.Getenv("FOLDER_RECONCILER_MAX_CONCURRENT_WORKERS")
if maxConcurrentWorkersStr == "" {
cfg.FolderReconciler.MaxConcurrentWorkers = 20
} else {
maxConcurrentWorkers, err := strconv.ParseUint(maxConcurrentWorkersStr, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid FOLDER_RECONCILER_MAX_CONCURRENT_WORKERS '%s': %w", maxConcurrentWorkersStr, err)
}
cfg.FolderReconciler.MaxConcurrentWorkers = maxConcurrentWorkers
}
return &cfg, nil
}

View File

@ -1,85 +0,0 @@
package main
import (
"fmt"
"net/http"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/transport"
"github.com/grafana/authlib/authn"
"github.com/grafana/grafana-app-sdk/plugin/kubeconfig"
)
// LoadInClusterConfig loads a kubernetes in-cluster config.
// Since the in-cluster config doesn't have a namespace, it defaults to "default"
func LoadInClusterConfig() (*kubeconfig.NamespacedConfig, error) {
cfg, err := rest.InClusterConfig()
if err != nil {
return nil, err
}
cfg.APIPath = "/apis"
return &kubeconfig.NamespacedConfig{
RestConfig: *cfg,
Namespace: "default",
}, nil
}
// LoadKubeConfigFromEnv loads a NamespacedConfig from the value of an environment variable
func LoadKubeConfigFromFolderAppURL(folderAppURL, exchangeUrl, authToken, namespace string) (*kubeconfig.NamespacedConfig, error) {
tokenExchangeClient, err := authn.NewTokenExchangeClient(authn.TokenExchangeConfig{
TokenExchangeURL: exchangeUrl,
Token: authToken,
})
if err != nil {
return nil, fmt.Errorf("failed to create token exchange client: %w", err)
}
return &kubeconfig.NamespacedConfig{
RestConfig: rest.Config{
APIPath: "/apis",
Host: folderAppURL,
WrapTransport: transport.WrapperFunc(func(rt http.RoundTripper) http.RoundTripper {
return &authRoundTripper{
tokenExchangeClient: tokenExchangeClient,
transport: rt,
}
}),
TLSClientConfig: rest.TLSClientConfig{
Insecure: true,
},
},
Namespace: namespace,
}, nil
}
// LoadKubeConfigFromFile loads a NamespacedConfig from a file on-disk (such as a mounted secret)
func LoadKubeConfigFromFile(configPath string) (*kubeconfig.NamespacedConfig, error) {
// Load the kubeconfig file
config, err := clientcmd.LoadFromFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to load kubeconfig from %s: %w", configPath, err)
}
// Build the REST config from the kubeconfig
restConfig, err := clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()
if err != nil {
return nil, fmt.Errorf("failed to create REST config: %w", err)
}
// Get the namespace from the current context, default to "default" if not set
namespace := "default"
if config.CurrentContext != "" {
if context, exists := config.Contexts[config.CurrentContext]; exists && context.Namespace != "" {
namespace = context.Namespace
}
}
restConfig.APIPath = "/apis"
return &kubeconfig.NamespacedConfig{
RestConfig: *restConfig,
Namespace: namespace,
}, nil
}

View File

@ -1,87 +0,0 @@
package main
import (
"context"
"log/slog"
"os"
"os/signal"
"github.com/grafana/grafana-app-sdk/k8s"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/simple"
"github.com/grafana/grafana/apps/iam/pkg/app"
"github.com/prometheus/client_golang/prometheus"
)
func main() {
// Configure the default logger to use slog
logging.DefaultLogger = logging.NewSLogLogger(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelDebug,
}))
//Load the config from the environment
cfg, err := LoadConfigFromEnv()
if err != nil {
logging.DefaultLogger.With("error", err).Error("Unable to load config from environment")
panic(err)
}
// Set up tracing
if cfg.OTelConfig.Host != "" {
err = simple.SetTraceProvider(simple.OpenTelemetryConfig{
Host: cfg.OTelConfig.Host,
Port: cfg.OTelConfig.Port,
ConnType: cfg.OTelConfig.ConnType,
ServiceName: cfg.OTelConfig.ServiceName,
})
if err != nil {
logging.DefaultLogger.With("error", err).Error("Unable to set trace provider")
panic(err)
}
}
// Create the operator config and the runner
operatorConfig := operator.RunnerConfig{
KubeConfig: cfg.KubeConfig.RestConfig,
WebhookConfig: operator.RunnerWebhookConfig{
Port: cfg.WebhookServer.Port,
TLSConfig: k8s.TLSConfig{
CertPath: cfg.WebhookServer.TLSCertPath,
KeyPath: cfg.WebhookServer.TLSKeyPath,
},
},
MetricsConfig: operator.RunnerMetricsConfig{
Enabled: true,
},
}
runner, err := operator.NewRunner(operatorConfig)
if err != nil {
logging.DefaultLogger.With("error", err).Error("Unable to create operator runner")
panic(err)
}
// Context and cancel for the operator's Run method
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill)
defer cancel()
// Create app config from operator config
appCfg := app.AppConfig{
ZanzanaClientCfg: cfg.ZanzanaClient,
Namespace: cfg.FolderReconciler.Namespace,
InformerConfig: app.InformerConfig{
MaxConcurrentWorkers: cfg.FolderReconciler.MaxConcurrentWorkers,
},
MetricsRegisterer: prometheus.DefaultRegisterer,
}
// Run
logging.DefaultLogger.Info("Starting operator")
err = runner.Run(ctx, app.Provider(appCfg))
if err != nil {
logging.DefaultLogger.With("error", err).Error("Operator exited with error")
panic(err)
}
logging.DefaultLogger.Info("Normal operator exit")
}

View File

@ -21,18 +21,15 @@ replace github.com/grafana/grafana/pkg/aggregator => ../../pkg/aggregator
replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604
require (
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c
github.com/grafana/grafana v6.1.6+incompatible
github.com/grafana/grafana-app-sdk v0.45.0
github.com/grafana/grafana v0.0.0-00010101000000-000000000000
github.com/grafana/grafana-app-sdk v0.46.0
github.com/grafana/grafana-app-sdk/logging v0.45.0
github.com/grafana/grafana-app-sdk/plugin v0.45.0
github.com/grafana/grafana/apps/folder v0.0.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0
github.com/prometheus/client_golang v1.23.2
go.opentelemetry.io/otel v1.38.0
go.opentelemetry.io/otel/trace v1.38.0
k8s.io/apimachinery v0.34.1
k8s.io/client-go v0.34.1
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
)
@ -148,7 +145,7 @@ require (
github.com/dolthub/vitess v0.0.0-20250410090211-143e6b272ad4 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elazarl/goproxy v1.7.2 // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
@ -204,8 +201,9 @@ require (
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
github.com/googleapis/gax-go/v2 v2.14.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/grafana/alerting v0.0.0-20250925200825-7a889aa4934d // indirect
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 // indirect
github.com/grafana/alerting v0.0.0-20251002001425-eeed80da0165 // indirect
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect
github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37 // indirect
github.com/grafana/dataplane/sdata v0.0.9 // indirect
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect
github.com/grafana/grafana-aws-sdk v1.2.0 // indirect
@ -424,7 +422,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect
@ -439,6 +437,7 @@ require (
k8s.io/api v0.34.1 // indirect
k8s.io/apiextensions-apiserver v0.34.1 // indirect
k8s.io/apiserver v0.34.1 // indirect
k8s.io/client-go v0.34.1 // indirect
k8s.io/component-base v0.34.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kms v0.34.1 // indirect

View File

@ -438,8 +438,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/proto v1.13.2 h1:z/etSFO3uyXeuEsVPzfl56WNgzcvIr42aQazXaQmFZY=
github.com/emicklei/proto v1.13.2/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
@ -721,24 +721,22 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v1.5.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-20250925200825-7a889aa4934d h1:zzEty7HgfXbQ/RiBCJFMqaZiJlqiXuz/Zbc6/H6ksuM=
github.com/grafana/alerting v0.0.0-20250925200825-7a889aa4934d/go.mod h1:T5sitas9VhVj8/S9LeRLy6H75kTBdh/sCCqHo7gaQI8=
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
github.com/grafana/alerting v0.0.0-20251002001425-eeed80da0165 h1:wfehM99Xlpltl9MQx8SITkgFgHmPGqrXoBCVLk/Q6NA=
github.com/grafana/alerting v0.0.0-20251002001425-eeed80da0165/go.mod h1:VGjS5gDwWEADPP6pF/drqLxEImgeuHlEW5u8E5EfIrM=
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-20250926065801-df98203cff37 h1:qEwZ+7MbPjzRvTi31iT9w7NBhKIpKwZrFbYmOZLqkwA=
github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
github.com/grafana/dataplane/examples v0.0.1 h1:K9M5glueWyLoL4//H+EtTQq16lXuHLmOhb6DjSCahzA=
github.com/grafana/dataplane/examples v0.0.1/go.mod h1:h5YwY8s407/17XF5/dS8XrUtsTVV2RnuW8+m1Mp46mg=
github.com/grafana/dataplane/sdata v0.0.9 h1:AGL1LZnCUG4MnQtnWpBPbQ8ZpptaZs14w6kE/MWfg7s=
github.com/grafana/dataplane/sdata v0.0.9/go.mod h1:Jvs5ddpGmn6vcxT7tCTWAZ1mgi4sbcdFt9utQx5uMAU=
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.45.0 h1:niFqYovxuw9vnUB9qoxEgmupqriG7Gns9ZGwB2uuOyE=
github.com/grafana/grafana-app-sdk v0.45.0/go.mod h1:1pYGEBrgG8i6pKmmsNXvtAr15jZ4iLtyHU4yj7T6XaI=
github.com/grafana/grafana-app-sdk v0.46.0 h1:gvzQvCQgZJ/73BfAcbDt/6TAMhnVikVPxZt/UwDl+oc=
github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc=
github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xuHKJjnO95/2pGOoA8w=
github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grafana/grafana-app-sdk/plugin v0.45.0 h1:rK4FL5h7SqGBDeUdrUjHVGnwgN6w8deQAKx8gJ27Iew=
github.com/grafana/grafana-app-sdk/plugin v0.45.0/go.mod h1:fZ6lWVMWr0EpkmyocxZ7MTTc9x6b2jYM93+gjlMgVD4=
github.com/grafana/grafana-aws-sdk v1.2.0 h1:LLR4/g91WBuCRwm2cbWfCREq565+GxIFe08nqqIcIuw=
github.com/grafana/grafana-aws-sdk v1.2.0/go.mod h1:bBo7qOmM3f61vO+2JxTolNUph1l2TmtzmWcU9/Im+8A=
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0 h1:0TYrkzAc3u0HX+9GK86cGrLTUAcmQfl3/LEB3tL+SOA=
@ -1996,8 +1994,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=

View File

@ -4,12 +4,13 @@ TeamBindingSpec: {
#Subject: {
// uid of the identity
name: string
// permission of the identity in the team
permission: TeamPermission
}
subjects: [...#Subject]
subject: #Subject
teamRef: TeamRef
// permission of the identity in the team
permission: TeamPermission
}
TeamRef:{

63
apps/iam/local/Dockerfile Normal file
View File

@ -0,0 +1,63 @@
# Build stage
FROM golang:1.24.6-alpine AS builder
# Set working directory
WORKDIR /app
# Install bash (required for update-workspace.sh) and build tools (required for CGO)
RUN apk add --no-cache bash build-base
# Copy source code
COPY ./apps ./apps
COPY ./pkg ./pkg
COPY ./conf ./conf
COPY ./go.mod ./go.mod
COPY ./go.sum ./go.sum
COPY ./go.work ./go.work
COPY ./go.work.sum ./go.work.sum
COPY ./build.go ./build.go
COPY ./package.json ./package.json
# Update workspace
COPY scripts/go-workspace/update-workspace.sh ./scripts/go-workspace/update-workspace.sh
RUN bash ./scripts/go-workspace/update-workspace.sh
# Build the application in dev mode to output binaries directly to ./bin/
RUN go run build.go -dev build-backend
# Final stage
FROM alpine:latest
# Install ca-certificates for HTTPS requests and wget for health checks
RUN apk --no-cache add ca-certificates tzdata wget
# Create non-root user
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
# Set working directory
WORKDIR /usr/share/grafana
# Copy all built binaries and conf from builder stage
COPY --from=builder /app/bin/ ./bin/
COPY --from=builder /app/conf/ ./conf/
# Create necessary directories and add binaries to PATH
RUN mkdir -p /etc/grafana-config && \
chown -R appuser:appgroup /usr/share/grafana /etc/grafana-config
# Switch to non-root user
USER appuser
# Add binaries to PATH
ENV PATH="/usr/share/grafana/bin:${PATH}"
# Expose ports for metrics and profiling
EXPOSE 8080 6060
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:8080/metrics || exit 1
# Run the application with the command from the YAML (can be overridden)
CMD ["grafana", "server", "target", "--config=/etc/grafana-config/operator.ini", "--homepath=/usr/share/grafana"]

View File

@ -56,7 +56,8 @@ spec:
spec:
containers:
- command:
- grafana-server
- grafana
- server
- target
- --config=/etc/grafana-config/operator.ini
- --homepath=/usr/share/grafana
@ -78,7 +79,7 @@ spec:
secretKeyRef:
name: iam-operator-secrets
key: grpc_auth_token
image: grafana/grafana-dev:12.3.0-17863745596
image: iam-folder-reconciler
imagePullPolicy: IfNotPresent
name: iam-folder-reconciler
volumeMounts:

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type CoreRoleClient struct {
@ -76,24 +75,6 @@ func (c *CoreRoleClient) Patch(ctx context.Context, identifier resource.Identifi
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *CoreRoleClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus CoreRoleStatus, opts resource.UpdateOptions) (*CoreRole, error) {
return c.client.Update(ctx, &CoreRole{
TypeMeta: metav1.TypeMeta{
Kind: CoreRoleKind().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 *CoreRoleClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@ -21,8 +21,6 @@ type CoreRole struct {
// Spec is the spec of the CoreRole
Spec CoreRoleSpec `json:"spec" yaml:"spec"`
Status CoreRoleStatus `json:"status" yaml:"status"`
}
func (o *CoreRole) GetSpec() any {
@ -39,15 +37,11 @@ func (o *CoreRole) SetSpec(spec any) error {
}
func (o *CoreRole) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
return map[string]any{}
}
func (o *CoreRole) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
@ -55,13 +49,6 @@ func (o *CoreRole) GetSubresource(name string) (any, bool) {
func (o *CoreRole) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(CoreRoleStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type CoreRoleStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
@ -233,7 +220,6 @@ func (o *CoreRole) DeepCopyInto(dst *CoreRole) {
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
@ -305,15 +291,3 @@ func (s *CoreRoleSpec) DeepCopy() *CoreRoleSpec {
func (s *CoreRoleSpec) DeepCopyInto(dst *CoreRoleSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of CoreRoleStatus
func (s *CoreRoleStatus) DeepCopy() *CoreRoleStatus {
cpy := &CoreRoleStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies CoreRoleStatus into another CoreRoleStatus object
func (s *CoreRoleStatus) DeepCopyInto(dst *CoreRoleStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type GlobalRoleClient struct {
@ -76,24 +75,6 @@ func (c *GlobalRoleClient) Patch(ctx context.Context, identifier resource.Identi
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *GlobalRoleClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus GlobalRoleStatus, opts resource.UpdateOptions) (*GlobalRole, error) {
return c.client.Update(ctx, &GlobalRole{
TypeMeta: metav1.TypeMeta{
Kind: GlobalRoleKind().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 *GlobalRoleClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@ -21,8 +21,6 @@ type GlobalRole struct {
// Spec is the spec of the GlobalRole
Spec GlobalRoleSpec `json:"spec" yaml:"spec"`
Status GlobalRoleStatus `json:"status" yaml:"status"`
}
func (o *GlobalRole) GetSpec() any {
@ -39,15 +37,11 @@ func (o *GlobalRole) SetSpec(spec any) error {
}
func (o *GlobalRole) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
return map[string]any{}
}
func (o *GlobalRole) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
@ -55,13 +49,6 @@ func (o *GlobalRole) GetSubresource(name string) (any, bool) {
func (o *GlobalRole) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(GlobalRoleStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type GlobalRoleStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
@ -233,7 +220,6 @@ func (o *GlobalRole) DeepCopyInto(dst *GlobalRole) {
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
@ -305,15 +291,3 @@ func (s *GlobalRoleSpec) DeepCopy() *GlobalRoleSpec {
func (s *GlobalRoleSpec) DeepCopyInto(dst *GlobalRoleSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of GlobalRoleStatus
func (s *GlobalRoleStatus) DeepCopy() *GlobalRoleStatus {
cpy := &GlobalRoleStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies GlobalRoleStatus into another GlobalRoleStatus object
func (s *GlobalRoleStatus) DeepCopyInto(dst *GlobalRoleStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type GlobalRoleBindingClient struct {
@ -76,24 +75,6 @@ func (c *GlobalRoleBindingClient) Patch(ctx context.Context, identifier resource
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *GlobalRoleBindingClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus GlobalRoleBindingStatus, opts resource.UpdateOptions) (*GlobalRoleBinding, error) {
return c.client.Update(ctx, &GlobalRoleBinding{
TypeMeta: metav1.TypeMeta{
Kind: GlobalRoleBindingKind().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 *GlobalRoleBindingClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@ -21,8 +21,6 @@ type GlobalRoleBinding struct {
// Spec is the spec of the GlobalRoleBinding
Spec GlobalRoleBindingSpec `json:"spec" yaml:"spec"`
Status GlobalRoleBindingStatus `json:"status" yaml:"status"`
}
func (o *GlobalRoleBinding) GetSpec() any {
@ -39,15 +37,11 @@ func (o *GlobalRoleBinding) SetSpec(spec any) error {
}
func (o *GlobalRoleBinding) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
return map[string]any{}
}
func (o *GlobalRoleBinding) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
@ -55,13 +49,6 @@ func (o *GlobalRoleBinding) GetSubresource(name string) (any, bool) {
func (o *GlobalRoleBinding) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(GlobalRoleBindingStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type GlobalRoleBindingStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
@ -233,7 +220,6 @@ func (o *GlobalRoleBinding) DeepCopyInto(dst *GlobalRoleBinding) {
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
@ -305,15 +291,3 @@ func (s *GlobalRoleBindingSpec) DeepCopy() *GlobalRoleBindingSpec {
func (s *GlobalRoleBindingSpec) DeepCopyInto(dst *GlobalRoleBindingSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of GlobalRoleBindingStatus
func (s *GlobalRoleBindingStatus) DeepCopy() *GlobalRoleBindingStatus {
cpy := &GlobalRoleBindingStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies GlobalRoleBindingStatus into another GlobalRoleBindingStatus object
func (s *GlobalRoleBindingStatus) DeepCopyInto(dst *GlobalRoleBindingStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ResourcePermissionClient struct {
@ -76,24 +75,6 @@ func (c *ResourcePermissionClient) Patch(ctx context.Context, identifier resourc
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *ResourcePermissionClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus ResourcePermissionStatus, opts resource.UpdateOptions) (*ResourcePermission, error) {
return c.client.Update(ctx, &ResourcePermission{
TypeMeta: metav1.TypeMeta{
Kind: ResourcePermissionKind().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 *ResourcePermissionClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@ -21,8 +21,6 @@ type ResourcePermission struct {
// Spec is the spec of the ResourcePermission
Spec ResourcePermissionSpec `json:"spec" yaml:"spec"`
Status ResourcePermissionStatus `json:"status" yaml:"status"`
}
func (o *ResourcePermission) GetSpec() any {
@ -39,15 +37,11 @@ func (o *ResourcePermission) SetSpec(spec any) error {
}
func (o *ResourcePermission) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
return map[string]any{}
}
func (o *ResourcePermission) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
@ -55,13 +49,6 @@ func (o *ResourcePermission) GetSubresource(name string) (any, bool) {
func (o *ResourcePermission) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(ResourcePermissionStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type ResourcePermissionStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
@ -233,7 +220,6 @@ func (o *ResourcePermission) DeepCopyInto(dst *ResourcePermission) {
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
@ -305,15 +291,3 @@ func (s *ResourcePermissionSpec) DeepCopy() *ResourcePermissionSpec {
func (s *ResourcePermissionSpec) DeepCopyInto(dst *ResourcePermissionSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of ResourcePermissionStatus
func (s *ResourcePermissionStatus) DeepCopy() *ResourcePermissionStatus {
cpy := &ResourcePermissionStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies ResourcePermissionStatus into another ResourcePermissionStatus object
func (s *ResourcePermissionStatus) DeepCopyInto(dst *ResourcePermissionStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type RoleClient struct {
@ -76,24 +75,6 @@ func (c *RoleClient) Patch(ctx context.Context, identifier resource.Identifier,
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *RoleClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus RoleStatus, opts resource.UpdateOptions) (*Role, error) {
return c.client.Update(ctx, &Role{
TypeMeta: metav1.TypeMeta{
Kind: RoleKind().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 *RoleClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@ -21,8 +21,6 @@ type Role struct {
// Spec is the spec of the Role
Spec RoleSpec `json:"spec" yaml:"spec"`
Status RoleStatus `json:"status" yaml:"status"`
}
func (o *Role) GetSpec() any {
@ -39,15 +37,11 @@ func (o *Role) SetSpec(spec any) error {
}
func (o *Role) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
return map[string]any{}
}
func (o *Role) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
@ -55,13 +49,6 @@ func (o *Role) GetSubresource(name string) (any, bool) {
func (o *Role) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(RoleStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type RoleStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
@ -233,7 +220,6 @@ func (o *Role) DeepCopyInto(dst *Role) {
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
@ -305,15 +291,3 @@ func (s *RoleSpec) DeepCopy() *RoleSpec {
func (s *RoleSpec) DeepCopyInto(dst *RoleSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of RoleStatus
func (s *RoleStatus) DeepCopy() *RoleStatus {
cpy := &RoleStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies RoleStatus into another RoleStatus object
func (s *RoleStatus) DeepCopyInto(dst *RoleStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type RoleBindingClient struct {
@ -76,24 +75,6 @@ func (c *RoleBindingClient) Patch(ctx context.Context, identifier resource.Ident
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *RoleBindingClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus RoleBindingStatus, opts resource.UpdateOptions) (*RoleBinding, error) {
return c.client.Update(ctx, &RoleBinding{
TypeMeta: metav1.TypeMeta{
Kind: RoleBindingKind().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 *RoleBindingClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@ -21,8 +21,6 @@ type RoleBinding struct {
// Spec is the spec of the RoleBinding
Spec RoleBindingSpec `json:"spec" yaml:"spec"`
Status RoleBindingStatus `json:"status" yaml:"status"`
}
func (o *RoleBinding) GetSpec() any {
@ -39,15 +37,11 @@ func (o *RoleBinding) SetSpec(spec any) error {
}
func (o *RoleBinding) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
return map[string]any{}
}
func (o *RoleBinding) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
@ -55,13 +49,6 @@ func (o *RoleBinding) GetSubresource(name string) (any, bool) {
func (o *RoleBinding) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(RoleBindingStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type RoleBindingStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
@ -233,7 +220,6 @@ func (o *RoleBinding) DeepCopyInto(dst *RoleBinding) {
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
@ -305,15 +291,3 @@ func (s *RoleBindingSpec) DeepCopy() *RoleBindingSpec {
func (s *RoleBindingSpec) DeepCopyInto(dst *RoleBindingSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of RoleBindingStatus
func (s *RoleBindingStatus) DeepCopy() *RoleBindingStatus {
cpy := &RoleBindingStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies RoleBindingStatus into another RoleBindingStatus object
func (s *RoleBindingStatus) DeepCopyInto(dst *RoleBindingStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ServiceAccountClient struct {
@ -76,24 +75,6 @@ func (c *ServiceAccountClient) Patch(ctx context.Context, identifier resource.Id
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *ServiceAccountClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus ServiceAccountStatus, opts resource.UpdateOptions) (*ServiceAccount, error) {
return c.client.Update(ctx, &ServiceAccount{
TypeMeta: metav1.TypeMeta{
Kind: ServiceAccountKind().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 *ServiceAccountClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@ -21,8 +21,6 @@ type ServiceAccount struct {
// Spec is the spec of the ServiceAccount
Spec ServiceAccountSpec `json:"spec" yaml:"spec"`
Status ServiceAccountStatus `json:"status" yaml:"status"`
}
func (o *ServiceAccount) GetSpec() any {
@ -39,15 +37,11 @@ func (o *ServiceAccount) SetSpec(spec any) error {
}
func (o *ServiceAccount) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
return map[string]any{}
}
func (o *ServiceAccount) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
@ -55,13 +49,6 @@ func (o *ServiceAccount) GetSubresource(name string) (any, bool) {
func (o *ServiceAccount) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(ServiceAccountStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type ServiceAccountStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
@ -233,7 +220,6 @@ func (o *ServiceAccount) DeepCopyInto(dst *ServiceAccount) {
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
@ -305,15 +291,3 @@ func (s *ServiceAccountSpec) DeepCopy() *ServiceAccountSpec {
func (s *ServiceAccountSpec) DeepCopyInto(dst *ServiceAccountSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of ServiceAccountStatus
func (s *ServiceAccountStatus) DeepCopy() *ServiceAccountStatus {
cpy := &ServiceAccountStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies ServiceAccountStatus into another ServiceAccountStatus object
func (s *ServiceAccountStatus) DeepCopyInto(dst *ServiceAccountStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type TeamClient struct {
@ -76,24 +75,6 @@ func (c *TeamClient) Patch(ctx context.Context, identifier resource.Identifier,
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *TeamClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus TeamStatus, opts resource.UpdateOptions) (*Team, error) {
return c.client.Update(ctx, &Team{
TypeMeta: metav1.TypeMeta{
Kind: TeamKind().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 *TeamClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@ -21,8 +21,6 @@ type Team struct {
// Spec is the spec of the Team
Spec TeamSpec `json:"spec" yaml:"spec"`
Status TeamStatus `json:"status" yaml:"status"`
}
func (o *Team) GetSpec() any {
@ -39,15 +37,11 @@ func (o *Team) SetSpec(spec any) error {
}
func (o *Team) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
return map[string]any{}
}
func (o *Team) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
@ -55,13 +49,6 @@ func (o *Team) GetSubresource(name string) (any, bool) {
func (o *Team) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(TeamStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type TeamStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
@ -233,7 +220,6 @@ func (o *Team) DeepCopyInto(dst *Team) {
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
@ -305,15 +291,3 @@ func (s *TeamSpec) DeepCopy() *TeamSpec {
func (s *TeamSpec) DeepCopyInto(dst *TeamSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of TeamStatus
func (s *TeamStatus) DeepCopy() *TeamStatus {
cpy := &TeamStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies TeamStatus into another TeamStatus object
func (s *TeamStatus) DeepCopyInto(dst *TeamStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type TeamBindingClient struct {
@ -76,24 +75,6 @@ func (c *TeamBindingClient) Patch(ctx context.Context, identifier resource.Ident
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *TeamBindingClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus TeamBindingStatus, opts resource.UpdateOptions) (*TeamBinding, error) {
return c.client.Update(ctx, &TeamBinding{
TypeMeta: metav1.TypeMeta{
Kind: TeamBindingKind().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 *TeamBindingClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@ -21,8 +21,6 @@ type TeamBinding struct {
// Spec is the spec of the TeamBinding
Spec TeamBindingSpec `json:"spec" yaml:"spec"`
Status TeamBindingStatus `json:"status" yaml:"status"`
}
func (o *TeamBinding) GetSpec() any {
@ -39,15 +37,11 @@ func (o *TeamBinding) SetSpec(spec any) error {
}
func (o *TeamBinding) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
return map[string]any{}
}
func (o *TeamBinding) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
@ -55,13 +49,6 @@ func (o *TeamBinding) GetSubresource(name string) (any, bool) {
func (o *TeamBinding) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(TeamBindingStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type TeamBindingStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
@ -233,7 +220,6 @@ func (o *TeamBinding) DeepCopyInto(dst *TeamBinding) {
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
@ -305,15 +291,3 @@ func (s *TeamBindingSpec) DeepCopy() *TeamBindingSpec {
func (s *TeamBindingSpec) DeepCopyInto(dst *TeamBindingSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of TeamBindingStatus
func (s *TeamBindingStatus) DeepCopy() *TeamBindingStatus {
cpy := &TeamBindingStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies TeamBindingStatus into another TeamBindingStatus object
func (s *TeamBindingStatus) DeepCopyInto(dst *TeamBindingStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@ -6,8 +6,6 @@ package v0alpha1
type TeamBindingspecSubject struct {
// uid of the identity
Name string `json:"name"`
// permission of the identity in the team
Permission TeamBindingTeamPermission `json:"permission"`
}
// NewTeamBindingspecSubject creates a new TeamBindingspecSubject object.
@ -15,14 +13,6 @@ func NewTeamBindingspecSubject() *TeamBindingspecSubject {
return &TeamBindingspecSubject{}
}
// +k8s:openapi-gen=true
type TeamBindingTeamPermission string
const (
TeamBindingTeamPermissionAdmin TeamBindingTeamPermission = "admin"
TeamBindingTeamPermissionMember TeamBindingTeamPermission = "member"
)
// +k8s:openapi-gen=true
type TeamBindingTeamRef struct {
// Name is the unique identifier for a team.
@ -34,16 +24,26 @@ func NewTeamBindingTeamRef() *TeamBindingTeamRef {
return &TeamBindingTeamRef{}
}
// +k8s:openapi-gen=true
type TeamBindingTeamPermission string
const (
TeamBindingTeamPermissionAdmin TeamBindingTeamPermission = "admin"
TeamBindingTeamPermissionMember TeamBindingTeamPermission = "member"
)
// +k8s:openapi-gen=true
type TeamBindingSpec struct {
Subjects []TeamBindingspecSubject `json:"subjects"`
Subject TeamBindingspecSubject `json:"subject"`
TeamRef TeamBindingTeamRef `json:"teamRef"`
// permission of the identity in the team
Permission TeamBindingTeamPermission `json:"permission"`
}
// NewTeamBindingSpec creates a new TeamBindingSpec object.
func NewTeamBindingSpec() *TeamBindingSpec {
return &TeamBindingSpec{
Subjects: []TeamBindingspecSubject{},
Subject: *NewTeamBindingspecSubject(),
TeamRef: *NewTeamBindingTeamRef(),
}
}

View File

@ -4,7 +4,6 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type UserClient struct {
@ -76,24 +75,6 @@ func (c *UserClient) Patch(ctx context.Context, identifier resource.Identifier,
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *UserClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus UserStatus, opts resource.UpdateOptions) (*User, error) {
return c.client.Update(ctx, &User{
TypeMeta: metav1.TypeMeta{
Kind: UserKind().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 *UserClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@ -21,8 +21,6 @@ type User struct {
// Spec is the spec of the User
Spec UserSpec `json:"spec" yaml:"spec"`
Status UserStatus `json:"status" yaml:"status"`
}
func (o *User) GetSpec() any {
@ -39,15 +37,11 @@ func (o *User) SetSpec(spec any) error {
}
func (o *User) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
return map[string]any{}
}
func (o *User) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
@ -55,13 +49,6 @@ func (o *User) GetSubresource(name string) (any, bool) {
func (o *User) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(UserStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type UserStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
@ -233,7 +220,6 @@ func (o *User) DeepCopyInto(dst *User) {
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
@ -305,15 +291,3 @@ func (s *UserSpec) DeepCopy() *UserSpec {
func (s *UserSpec) DeepCopyInto(dst *UserSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of UserStatus
func (s *UserStatus) DeepCopy() *UserStatus {
cpy := &UserStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies UserStatus into another UserStatus object
func (s *UserStatus) DeepCopyInto(dst *UserStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@ -109,18 +109,12 @@ func schema_pkg_apis_iam_v0alpha1_CoreRole(ref common.ReferenceCallback) common.
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@ -387,18 +381,12 @@ func schema_pkg_apis_iam_v0alpha1_GlobalRole(ref common.ReferenceCallback) commo
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@ -435,18 +423,12 @@ func schema_pkg_apis_iam_v0alpha1_GlobalRoleBinding(ref common.ReferenceCallback
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@ -948,18 +930,12 @@ func schema_pkg_apis_iam_v0alpha1_ResourcePermission(ref common.ReferenceCallbac
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ResourcePermissionSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@ -1247,18 +1223,12 @@ func schema_pkg_apis_iam_v0alpha1_Role(ref common.ReferenceCallback) common.Open
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@ -1295,18 +1265,12 @@ func schema_pkg_apis_iam_v0alpha1_RoleBinding(ref common.ReferenceCallback) comm
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@ -1808,18 +1772,12 @@ func schema_pkg_apis_iam_v0alpha1_ServiceAccount(ref common.ReferenceCallback) c
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ServiceAccountSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@ -2040,18 +1998,12 @@ func schema_pkg_apis_iam_v0alpha1_Team(ref common.ReferenceCallback) common.Open
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@ -2088,18 +2040,12 @@ func schema_pkg_apis_iam_v0alpha1_TeamBinding(ref common.ReferenceCallback) comm
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@ -2157,27 +2103,28 @@ func schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref common.ReferenceCallback)
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"subjects": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
"subject": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject"),
},
},
},
},
},
"teamRef": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingTeamRef"),
},
},
"permission": {
SchemaProps: spec.SchemaProps{
Description: "permission of the identity in the team",
Default: "",
Type: []string{"string"},
Format: "",
},
Required: []string{"subjects", "teamRef"},
},
},
Required: []string{"subject", "teamRef", "permission"},
},
},
Dependencies: []string{
@ -2264,16 +2211,8 @@ func schema_pkg_apis_iam_v0alpha1_TeamBindingspecSubject(ref common.ReferenceCal
Format: "",
},
},
"permission": {
SchemaProps: spec.SchemaProps{
Description: "permission of the identity in the team",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"name", "permission"},
Required: []string{"name"},
},
},
}
@ -2547,18 +2486,12 @@ func schema_pkg_apis_iam_v0alpha1_User(ref common.ReferenceCallback) common.Open
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus"),
},
},
},
Required: []string{"metadata", "spec", "status"},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec", "github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.UserSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}

View File

@ -20,6 +20,7 @@ import (
var appManifestData = app.ManifestData{
AppName: "iam",
Group: "iam.grafana.app",
PreferredVersion: "v0alpha1",
Versions: []app.ManifestVersion{
{
Name: "v0alpha1",

View File

@ -36,7 +36,7 @@ func Provider(appCfg app.SpecificConfig) app.Provider {
}
func generateInformerSupplier(informerConfig InformerConfig, metrics *reconcilers.ReconcilerMetrics) simple.InformerSupplier {
return func(kind resource.Kind, clients resource.ClientGenerator, options operator.ListWatchOptions) (operator.Informer, error) {
return func(kind resource.Kind, clients resource.ClientGenerator, options operator.InformerOptions) (operator.Informer, error) {
client, err := clients.ClientFor(kind)
if err != nil {
return nil, err
@ -44,9 +44,7 @@ func generateInformerSupplier(informerConfig InformerConfig, metrics *reconciler
informer, err := operator.NewKubernetesBasedInformer(
kind, client,
operator.KubernetesBasedInformerOptions{
ListWatchOptions: options,
},
options,
)
if err != nil {
return nil, err
@ -79,7 +77,6 @@ func New(cfg app.Config) (app.App, error) {
folderReconciler, err := reconcilers.NewFolderReconciler(reconcilers.ReconcilerConfig{
ZanzanaCfg: appSpecificConfig.ZanzanaClientCfg,
KubeConfig: &cfg.KubeConfig,
Metrics: metrics,
})
if err != nil {
@ -93,6 +90,7 @@ func New(cfg app.Config) (app.App, error) {
KubeConfig: cfg.KubeConfig,
InformerConfig: simple.AppInformerConfig{
InformerSupplier: generateInformerSupplier(appSpecificConfig.InformerConfig, metrics),
InformerOptions: operator.InformerOptions{
ErrorHandler: func(ctx context.Context, err error) {
logging.FromContext(ctx).With("error", err).Error("Informer processing error")
if metrics != nil {
@ -101,6 +99,7 @@ func New(cfg app.Config) (app.App, error) {
}
},
},
},
UnmanagedKinds: []simple.AppUnmanagedKind{
{
Kind: foldersKind.FolderKind(),

View File

@ -8,19 +8,14 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
foldersKind "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/services/authz"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
"k8s.io/client-go/rest"
)
// FolderStore interface for retrieving folder information
type FolderStore interface {
GetFolderParent(ctx context.Context, namespace, uid string) (string, error)
}
// PermissionStore interface for managing folder permissions
type PermissionStore interface {
GetFolderParents(ctx context.Context, namespace, folderUID string) ([]string, error)
@ -31,13 +26,11 @@ type PermissionStore interface {
// ReconcilerConfig represents the app-specific configuration
type ReconcilerConfig struct {
ZanzanaCfg authz.ZanzanaClientConfig
KubeConfig *rest.Config
Metrics *ReconcilerMetrics
}
type FolderReconciler struct {
permissionStore PermissionStore
folderStore FolderStore
metrics *ReconcilerMetrics
}
@ -50,12 +43,10 @@ func NewFolderReconciler(cfg ReconcilerConfig) (operator.Reconciler, error) {
}
// Create dependencies
folderStore := NewAPIFolderStore(cfg.KubeConfig)
permissionStore := NewZanzanaPermissionStore(zanzanaClient)
folderReconciler := &FolderReconciler{
permissionStore: permissionStore,
folderStore: folderStore,
metrics: cfg.Metrics,
}
@ -137,11 +128,11 @@ func (r *FolderReconciler) handleUpdateFolder(ctx context.Context, folder *folde
folderUID := folder.Name
namespace := folder.Namespace
parentUID, err := r.folderStore.GetFolderParent(ctx, namespace, folderUID)
parentUID, err := getFolderParent(ctx, folder)
if err != nil {
logger.Error("Error getting folder parent", "error", err)
if r.metrics != nil {
r.metrics.RecordReconcileFailure(action, "folder_store")
r.metrics.RecordReconcileFailure(action, "failure_informer")
}
return operator.ReconcileResult{}, err
}
@ -217,3 +208,21 @@ func validateFolder(folder *foldersKind.Folder) error {
}
return nil
}
func getFolderParent(ctx context.Context, folder *foldersKind.Folder) (string, error) {
tracer := otel.GetTracerProvider().Tracer("iam-folder-reconciler")
_, span := tracer.Start(ctx, "get-folder-parent",
trace.WithAttributes(
attribute.String("folder.uid", folder.Name),
),
)
defer span.End()
folderMeta, err := utils.MetaAccessor(folder)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "failed to get folder meta accessor")
return "", err
}
return folderMeta.GetFolder(), nil
}

View File

@ -1,76 +0,0 @@
package reconcilers
import (
"context"
"fmt"
foldersKind "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
)
var _ FolderStore = (*APIFolderStore)(nil)
func NewAPIFolderStore(config *rest.Config) FolderStore {
return &APIFolderStore{config}
}
type APIFolderStore struct {
config *rest.Config
}
func (s *APIFolderStore) GetFolderParent(ctx context.Context, namespace, uid string) (string, error) {
tracer := otel.GetTracerProvider().Tracer("iam-folder-reconciler")
ctx, span := tracer.Start(ctx, "APIFolderStore.GetFolderParent",
trace.WithAttributes(
attribute.String("folder.uid", uid),
attribute.String("folder.namespace", namespace),
),
)
defer span.End()
client, err := s.client(namespace)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "failed to create kubernetes client")
return "", fmt.Errorf("create resource client: %w", err)
}
// Get the folder by UID
unstructuredObj, err := client.Get(ctx, uid, metav1.GetOptions{})
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "failed to get folder from kubernetes API")
return "", fmt.Errorf("get folder %s: %w", uid, err)
}
object, err := utils.MetaAccessor(unstructuredObj)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "failed to get meta accessor from folder object")
return "", fmt.Errorf("get meta accessor: %w", err)
}
parentUID := object.GetFolder()
span.SetAttributes(attribute.String("folder.parent_uid", parentUID))
span.SetStatus(codes.Ok, "successfully retrieved folder parent")
span.AddEvent("folder.parent.retrieved", trace.WithAttributes(
attribute.String("parent.uid", parentUID),
))
return parentUID, nil
}
func (s *APIFolderStore) client(namespace string) (dynamic.ResourceInterface, error) {
client, err := dynamic.NewForConfig(s.config)
if err != nil {
return nil, err
}
return client.Resource(foldersKind.FolderResourceInfo.GroupVersionResource()).Namespace(namespace), nil
}

View File

@ -22,7 +22,6 @@ func NewReconcilerMetrics(registerer prometheus.Registerer, namespace string) *R
"success_no_changes_needed",
"failure_informer",
"failure_permission_store",
"failure_folder_store",
"failure_unknown",
},
}

View File

@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/investigations
go 1.24.6
require (
github.com/grafana/grafana-app-sdk v0.45.0
github.com/grafana/grafana-app-sdk v0.46.0
k8s.io/apimachinery v0.34.1
k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
@ -15,7 +15,7 @@ require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
@ -78,7 +78,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.34.1 // indirect

View File

@ -10,8 +10,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@ -46,8 +46,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/grafana-app-sdk v0.45.0 h1:niFqYovxuw9vnUB9qoxEgmupqriG7Gns9ZGwB2uuOyE=
github.com/grafana/grafana-app-sdk v0.45.0/go.mod h1:1pYGEBrgG8i6pKmmsNXvtAr15jZ4iLtyHU4yj7T6XaI=
github.com/grafana/grafana-app-sdk v0.46.0 h1:gvzQvCQgZJ/73BfAcbDt/6TAMhnVikVPxZt/UwDl+oc=
github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc=
github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xuHKJjnO95/2pGOoA8w=
github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
@ -203,8 +203,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -76,7 +76,7 @@ func (c *InvestigationClient) Patch(ctx context.Context, identifier resource.Ide
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *InvestigationClient) UpdateStatus(ctx context.Context, newStatus InvestigationStatus, opts resource.UpdateOptions) (*Investigation, error) {
func (c *InvestigationClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus InvestigationStatus, opts resource.UpdateOptions) (*Investigation, error) {
return c.client.Update(ctx, &Investigation{
TypeMeta: metav1.TypeMeta{
Kind: InvestigationKind().Kind(),
@ -84,6 +84,8 @@ func (c *InvestigationClient) UpdateStatus(ctx context.Context, newStatus Invest
},
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: opts.ResourceVersion,
Namespace: identifier.Namespace,
Name: identifier.Name,
},
Status: newStatus,
}, resource.UpdateOptions{

View File

@ -76,7 +76,7 @@ func (c *InvestigationIndexClient) Patch(ctx context.Context, identifier resourc
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *InvestigationIndexClient) UpdateStatus(ctx context.Context, newStatus InvestigationIndexStatus, opts resource.UpdateOptions) (*InvestigationIndex, error) {
func (c *InvestigationIndexClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus InvestigationIndexStatus, opts resource.UpdateOptions) (*InvestigationIndex, error) {
return c.client.Update(ctx, &InvestigationIndex{
TypeMeta: metav1.TypeMeta{
Kind: InvestigationIndexKind().Kind(),
@ -84,6 +84,8 @@ func (c *InvestigationIndexClient) UpdateStatus(ctx context.Context, newStatus I
},
ObjectMeta: metav1.ObjectMeta{
ResourceVersion: opts.ResourceVersion,
Namespace: identifier.Namespace,
Name: identifier.Name,
},
Status: newStatus,
}, resource.UpdateOptions{

View File

@ -13,6 +13,7 @@ import (
"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"
v0alpha1 "github.com/grafana/grafana/apps/investigations/pkg/apis/investigations/v0alpha1"
)
@ -29,6 +30,7 @@ var (
var appManifestData = app.ManifestData{
AppName: "investigations",
Group: "investigations.grafana.app",
PreferredVersion: "v0alpha1",
Versions: []app.ManifestVersion{
{
Name: "v0alpha1",
@ -50,6 +52,10 @@ var appManifestData = app.ManifestData{
Schema: &versionSchemaInvestigationIndexv0alpha1,
},
},
Routes: app.ManifestVersionRoutes{
Namespaced: map[string]spec3.PathProps{},
Cluster: map[string]spec3.PathProps{},
},
},
},
}
@ -79,6 +85,7 @@ 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:]
@ -97,8 +104,22 @@ func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goTyp
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)
}
@ -108,3 +129,6 @@ func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb str
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)
}

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana-app-sdk/simple"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -18,10 +19,12 @@ func New(cfg app.Config) (app.App, error) {
Name: "investigation",
KubeConfig: cfg.KubeConfig,
InformerConfig: simple.AppInformerConfig{
InformerOptions: operator.InformerOptions{
ErrorHandler: func(_ context.Context, err error) {
klog.ErrorS(err, "Informer processing error")
},
},
},
ManagedKinds: []simple.AppManagedKind{
{
Kind: investigationsv0alpha1.InvestigationKind(),

View File

@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/playlist
go 1.24.6
require (
github.com/grafana/grafana-app-sdk v0.45.0
github.com/grafana/grafana-app-sdk v0.46.0
k8s.io/apimachinery v0.34.1
k8s.io/client-go v0.34.1
k8s.io/klog/v2 v2.130.1
@ -16,7 +16,7 @@ require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
@ -79,7 +79,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.34.1 // indirect

View File

@ -10,8 +10,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@ -46,8 +46,8 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/grafana-app-sdk v0.45.0 h1:niFqYovxuw9vnUB9qoxEgmupqriG7Gns9ZGwB2uuOyE=
github.com/grafana/grafana-app-sdk v0.45.0/go.mod h1:1pYGEBrgG8i6pKmmsNXvtAr15jZ4iLtyHU4yj7T6XaI=
github.com/grafana/grafana-app-sdk v0.46.0 h1:gvzQvCQgZJ/73BfAcbDt/6TAMhnVikVPxZt/UwDl+oc=
github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc=
github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xuHKJjnO95/2pGOoA8w=
github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
@ -203,8 +203,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -27,6 +27,7 @@ var (
var appManifestData = app.ManifestData{
AppName: "playlist",
Group: "playlist.grafana.app",
PreferredVersion: "v0alpha1",
Versions: []app.ManifestVersion{
{
Name: "v0alpha1",

View File

@ -50,10 +50,12 @@ func New(cfg app.Config) (app.App, error) {
Name: "playlist",
KubeConfig: cfg.KubeConfig,
InformerConfig: simple.AppInformerConfig{
InformerOptions: operator.InformerOptions{
ErrorHandler: func(ctx context.Context, err error) {
klog.ErrorS(err, "Informer processing error")
},
},
},
ManagedKinds: []simple.AppManagedKind{
{
Kind: playlistv0alpha1.PlaylistKind(),

View File

@ -3,8 +3,8 @@ module github.com/grafana/grafana/apps/plugins
go 1.24.4
require (
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781
github.com/grafana/grafana-app-sdk v0.45.0
github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37
github.com/grafana/grafana-app-sdk v0.46.0
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde
k8s.io/apimachinery v0.34.1
k8s.io/apiserver v0.34.1
@ -19,7 +19,7 @@ require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
github.com/emicklei/go-restful/v3 v3.13.0 // indirect
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
github.com/getkin/kin-openapi v0.133.0 // indirect
@ -35,7 +35,7 @@ require (
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c // indirect
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // indirect
github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect
github.com/grafana/grafana-app-sdk/logging v0.45.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
@ -86,7 +86,7 @@ require (
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/grpc v1.75.1 // indirect
google.golang.org/protobuf v1.36.8 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.34.1 // indirect

View File

@ -12,8 +12,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
@ -50,14 +50,14 @@ github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J
github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c h1:8GIMe1KclDdfogaeRsiU69Ev2zTF9kmjqjQqqZMzerc=
github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c/go.mod h1:C6CmTG6vfiqebjJswKsc6zes+1F/OtTCi6aAtL5Um6A=
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 h1:jymmOFIWnW26DeUjFgYEoltI170KeT5r1rI8a/dUf0E=
github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
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-20250926065801-df98203cff37 h1:qEwZ+7MbPjzRvTi31iT9w7NBhKIpKwZrFbYmOZLqkwA=
github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37/go.mod h1:qeWYbnWzaYGl88JlL9+DsP1GT2Cudm58rLtx13fKZdw=
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.45.0 h1:niFqYovxuw9vnUB9qoxEgmupqriG7Gns9ZGwB2uuOyE=
github.com/grafana/grafana-app-sdk v0.45.0/go.mod h1:1pYGEBrgG8i6pKmmsNXvtAr15jZ4iLtyHU4yj7T6XaI=
github.com/grafana/grafana-app-sdk v0.46.0 h1:gvzQvCQgZJ/73BfAcbDt/6TAMhnVikVPxZt/UwDl+oc=
github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc=
github.com/grafana/grafana-app-sdk/logging v0.45.0 h1:0SH6nYZpiLBZRwUq4J6+1vo8xuHKJjnO95/2pGOoA8w=
github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250428110029-a8ea72012bde h1:ydSrBIOCxJQ84+JU+cyYsOLL40QeXrB7rYfsY/ezU4w=
@ -219,8 +219,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20250908214217-97024824d090/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -30,6 +30,7 @@ var (
var appManifestData = app.ManifestData{
AppName: "plugins",
Group: "plugins.grafana.app",
PreferredVersion: "v0alpha1",
Versions: []app.ManifestVersion{
{
Name: "v0alpha1",

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana-app-sdk/simple"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -26,10 +27,12 @@ func New(cfg app.Config) (app.App, error) {
Name: "plugins",
KubeConfig: cfg.KubeConfig,
InformerConfig: simple.AppInformerConfig{
InformerOptions: operator.InformerOptions{
ErrorHandler: func(ctx context.Context, err error) {
klog.ErrorS(err, "Informer processing error")
},
},
},
ManagedKinds: managedKinds,
}

View File

@ -6,4 +6,5 @@ generate: install-app-sdk update-app-sdk
--source=./kinds/ \
--gogenpath=./pkg/apis \
--grouping=group \
--genoperatorstate=false \
--defencoding=none

Some files were not shown because too many files have changed in this diff Show More