diff --git a/.air.toml b/.air.toml index dd0faa01f23..2959e88838e 100644 --- a/.air.toml +++ b/.air.toml @@ -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 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 85db1f8d4c7..72635653fe4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -32,7 +32,9 @@ /devenv/README.md @grafana/docs-grafana # START Technical documentation -/.vale.ini @grafana/docs-tooling +/.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 diff --git a/.github/workflows/feature-toggles-ci.yml b/.github/workflows/feature-toggles-ci.yml index 67d1d37c739..d8808d86d67 100644 --- a/.github/workflows/feature-toggles-ci.yml +++ b/.github/workflows/feature-toggles-ci.yml @@ -11,6 +11,8 @@ permissions: {} jobs: test: + name: Feature toggles documentation is in sync with source + runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/pr-e2e-tests.yml b/.github/workflows/pr-e2e-tests.yml index 6a61f3bf5ca..6ade5a9c787 100644 --- a/.github/workflows/pr-e2e-tests.yml +++ b/.github/workflows/pr-e2e-tests.yml @@ -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 \ diff --git a/.github/workflows/release-npm.yml b/.github/workflows/release-npm.yml index e4bed54a7f8..4dd92e9d0a1 100644 --- a/.github/workflows/release-npm.yml +++ b/.github/workflows/release-npm.yml @@ -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 - diff --git a/.github/workflows/storybook-a11y.yml b/.github/workflows/storybook-a11y.yml index 9a36f7aefdf..68ff4e75e37 100644 --- a/.github/workflows/storybook-a11y.yml +++ b/.github/workflows/storybook-a11y.yml @@ -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 diff --git a/.github/workflows/verify-kinds.yml b/.github/workflows/verify-kinds.yml index 6319baa51a9..0fe82c5e67e 100644 --- a/.github/workflows/verify-kinds.yml +++ b/.github/workflows/verify-kinds.yml @@ -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" diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..5d12c20603b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,297 @@ +# AGENTS.md + + + + + +## 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: + +- +- +- ... +``` + +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 _``_ +- 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: + + +``` + +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= +OTEL_EXPORTER_OTLP_ENDPOINT= +``` + +The placeholder includes the name and the less than and greater than symbols, +for example . + +If the placeholder is markdown emphasize it with underscores, +for example _``_. + +In code blocks use the placeholder without additional backticks or emphasis, +for example . + +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 + + +This code sets required environment variables +to send OTLP data to an OTLP endpoint. +To configure the code refer to the configuration section. + + +``` + +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: + + +``` + +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 `` as "note", "caution", or "warning": + +```markdown +{{< admonition type="" >}} +... +{{< /admonition >}} +``` + +Use admonitions sparingly. +Only include exceptional information in admonitions. + + diff --git a/Makefile b/Makefile index b6ea79ea13e..e76e5a18940 100644 --- a/Makefile +++ b/Makefile @@ -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" diff --git a/apps/advisor/pkg/app/app.go b/apps/advisor/pkg/app/app.go index fae11df0c6c..b572f4b3866 100644 --- a/apps/advisor/pkg/app/app.go +++ b/apps/advisor/pkg/app/app.go @@ -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,8 +49,10 @@ func New(cfg app.Config) (app.App, error) { Name: "advisor", KubeConfig: cfg.KubeConfig, InformerConfig: simple.AppInformerConfig{ - ErrorHandler: func(ctx context.Context, err error) { - log.WithContext(ctx).Error("Informer processing error", "error", err) + InformerOptions: operator.InformerOptions{ + ErrorHandler: func(ctx context.Context, err error) { + log.WithContext(ctx).Error("Informer processing error", "error", err) + }, }, }, ManagedKinds: []simple.AppManagedKind{ diff --git a/apps/alerting/alertenrichment/go.mod b/apps/alerting/alertenrichment/go.mod index c36ab96836a..48c93f1aa1e 100644 --- a/apps/alerting/alertenrichment/go.mod +++ b/apps/alerting/alertenrichment/go.mod @@ -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 diff --git a/apps/alerting/alertenrichment/go.sum b/apps/alerting/alertenrichment/go.sum index 0f5080062e6..ead26b4a7b0 100644 --- a/apps/alerting/alertenrichment/go.sum +++ b/apps/alerting/alertenrichment/go.sum @@ -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= diff --git a/apps/alerting/notifications/definitions/alerting-notifications-manifest.yaml b/apps/alerting/notifications/definitions/alerting-notifications-manifest.yaml index 447c5d5f747..aa40d7c74b3 100644 --- a/apps/alerting/notifications/definitions/alerting-notifications-manifest.yaml +++ b/apps/alerting/notifications/definitions/alerting-notifications-manifest.yaml @@ -5,6 +5,7 @@ metadata: spec: appName: alerting-notifications group: notifications.alerting.grafana.app + preferredVersion: v0alpha1 versions: - kinds: - conversion: false diff --git a/apps/alerting/notifications/go.mod b/apps/alerting/notifications/go.mod index 36fc4da5f16..f811c12d2f8 100644 --- a/apps/alerting/notifications/go.mod +++ b/apps/alerting/notifications/go.mod @@ -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 diff --git a/apps/alerting/notifications/go.sum b/apps/alerting/notifications/go.sum index d8686fa8e79..9166be2eae7 100644 --- a/apps/alerting/notifications/go.sum +++ b/apps/alerting/notifications/go.sum @@ -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= diff --git a/apps/alerting/notifications/pkg/apis/alertingnotifications_manifest.go b/apps/alerting/notifications/pkg/apis/alertingnotifications_manifest.go index 3537a6e3e8d..84850cb31a1 100644 --- a/apps/alerting/notifications/pkg/apis/alertingnotifications_manifest.go +++ b/apps/alerting/notifications/pkg/apis/alertingnotifications_manifest.go @@ -34,8 +34,9 @@ var ( ) var appManifestData = app.ManifestData{ - AppName: "alerting-notifications", - Group: "notifications.alerting.grafana.app", + AppName: "alerting-notifications", + Group: "notifications.alerting.grafana.app", + PreferredVersion: "v0alpha1", Versions: []app.ManifestVersion{ { Name: "v0alpha1", diff --git a/apps/alerting/notifications/pkg/app/app.go b/apps/alerting/notifications/pkg/app/app.go index b57ed0cd143..65b3882ac59 100644 --- a/apps/alerting/notifications/pkg/app/app.go +++ b/apps/alerting/notifications/pkg/app/app.go @@ -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,8 +23,10 @@ func New(cfg app.Config) (app.App, error) { Name: "alerting.notification", KubeConfig: cfg.KubeConfig, InformerConfig: simple.AppInformerConfig{ - ErrorHandler: func(ctx context.Context, err error) { - logging.DefaultLogger.With("error", err).Error("Informer processing error") + InformerOptions: operator.InformerOptions{ + ErrorHandler: func(ctx context.Context, err error) { + logging.DefaultLogger.With("error", err).Error("Informer processing error") + }, }, }, ManagedKinds: managedKinds, diff --git a/apps/alerting/rules/definitions/alerting-manifest.yaml b/apps/alerting/rules/definitions/alerting-manifest.yaml index 22808e9deba..836bd2c9a94 100644 --- a/apps/alerting/rules/definitions/alerting-manifest.yaml +++ b/apps/alerting/rules/definitions/alerting-manifest.yaml @@ -5,6 +5,7 @@ metadata: spec: appName: alerting group: rules.alerting.grafana.app + preferredVersion: v0alpha1 versions: - kinds: - conversion: false diff --git a/apps/alerting/rules/go.mod b/apps/alerting/rules/go.mod index 16021cb3241..7678f821f3a 100644 --- a/apps/alerting/rules/go.mod +++ b/apps/alerting/rules/go.mod @@ -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 diff --git a/apps/alerting/rules/go.sum b/apps/alerting/rules/go.sum index b34bb79ec13..dadd72070fd 100644 --- a/apps/alerting/rules/go.sum +++ b/apps/alerting/rules/go.sum @@ -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= diff --git a/apps/alerting/rules/pkg/apis/alerting_manifest.go b/apps/alerting/rules/pkg/apis/alerting_manifest.go index abbc407ddf3..ea4f2530948 100644 --- a/apps/alerting/rules/pkg/apis/alerting_manifest.go +++ b/apps/alerting/rules/pkg/apis/alerting_manifest.go @@ -28,8 +28,9 @@ var ( ) var appManifestData = app.ManifestData{ - AppName: "alerting", - Group: "rules.alerting.grafana.app", + AppName: "alerting", + Group: "rules.alerting.grafana.app", + PreferredVersion: "v0alpha1", Versions: []app.ManifestVersion{ { Name: "v0alpha1", diff --git a/apps/alerting/rules/pkg/app/app.go b/apps/alerting/rules/pkg/app/app.go index 513bcc92d30..ef0ab1883cf 100644 --- a/apps/alerting/rules/pkg/app/app.go +++ b/apps/alerting/rules/pkg/app/app.go @@ -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,8 +23,10 @@ func New(cfg app.Config) (app.App, error) { Name: "alerting.rules", KubeConfig: cfg.KubeConfig, InformerConfig: simple.AppInformerConfig{ - ErrorHandler: func(ctx context.Context, err error) { - logging.DefaultLogger.With("error", err).Error("Informer processing error") + InformerOptions: operator.InformerOptions{ + ErrorHandler: func(ctx context.Context, err error) { + logging.DefaultLogger.With("error", err).Error("Informer processing error") + }, }, }, ManagedKinds: managedKinds, diff --git a/apps/correlations/Makefile b/apps/correlations/Makefile index 230bfd4149a..bc8d6d30cb5 100644 --- a/apps/correlations/Makefile +++ b/apps/correlations/Makefile @@ -6,4 +6,5 @@ generate: install-app-sdk update-app-sdk --source=./kinds/ \ --gogenpath=./pkg/apis \ --grouping=group \ + --genoperatorstate=false \ --defencoding=none \ No newline at end of file diff --git a/apps/correlations/go.mod b/apps/correlations/go.mod index 2e66709bda6..2f6538b879d 100644 --- a/apps/correlations/go.mod +++ b/apps/correlations/go.mod @@ -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 diff --git a/apps/correlations/go.sum b/apps/correlations/go.sum index b34bb79ec13..dadd72070fd 100644 --- a/apps/correlations/go.sum +++ b/apps/correlations/go.sum @@ -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= diff --git a/apps/correlations/pkg/apis/correlation/v0alpha1/correlation_client_gen.go b/apps/correlations/pkg/apis/correlation/v0alpha1/correlation_client_gen.go index 482d497e154..fe8f518b852 100644 --- a/apps/correlations/pkg/apis/correlation/v0alpha1/correlation_client_gen.go +++ b/apps/correlations/pkg/apis/correlation/v0alpha1/correlation_client_gen.go @@ -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) } diff --git a/apps/correlations/pkg/apis/correlation/v0alpha1/correlation_object_gen.go b/apps/correlations/pkg/apis/correlation/v0alpha1/correlation_object_gen.go index d10cb114aa8..3191e415977 100644 --- a/apps/correlations/pkg/apis/correlation/v0alpha1/correlation_object_gen.go +++ b/apps/correlations/pkg/apis/correlation/v0alpha1/correlation_object_gen.go @@ -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) -} diff --git a/apps/correlations/pkg/apis/correlation/v0alpha1/correlation_status_gen.go b/apps/correlations/pkg/apis/correlation/v0alpha1/correlation_status_gen.go deleted file mode 100644 index 5b8bc9f8088..00000000000 --- a/apps/correlations/pkg/apis/correlation/v0alpha1/correlation_status_gen.go +++ /dev/null @@ -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" -) diff --git a/apps/correlations/pkg/apis/correlation_manifest.go b/apps/correlations/pkg/apis/correlation_manifest.go index 293633bff4f..3a784903323 100644 --- a/apps/correlations/pkg/apis/correlation_manifest.go +++ b/apps/correlations/pkg/apis/correlation_manifest.go @@ -10,24 +10,24 @@ 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) ) var appManifestData = app.ManifestData{ - AppName: "correlation", - Group: "correlations.grafana.app", + AppName: "correlation", + Group: "correlations.grafana.app", + PreferredVersion: "v0alpha1", Versions: []app.ManifestVersion{ { Name: "v0alpha1", diff --git a/apps/correlations/pkg/app/app.go b/apps/correlations/pkg/app/app.go index 99a1600bac6..5d62c6c3902 100644 --- a/apps/correlations/pkg/app/app.go +++ b/apps/correlations/pkg/app/app.go @@ -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,8 +18,10 @@ func New(cfg app.Config) (app.App, error) { Name: "correlation", KubeConfig: cfg.KubeConfig, InformerConfig: simple.AppInformerConfig{ - ErrorHandler: func(ctx context.Context, err error) { - logging.FromContext(ctx).Error("Informer processing error", "error", err) + InformerOptions: operator.InformerOptions{ + ErrorHandler: func(ctx context.Context, err error) { + logging.FromContext(ctx).Error("Informer processing error", "error", err) + }, }, }, ManagedKinds: []simple.AppManagedKind{ diff --git a/apps/correlations/plugin/src/generated/correlation/v0alpha1/correlation_object_gen.ts b/apps/correlations/plugin/src/generated/correlation/v0alpha1/correlation_object_gen.ts index ff5b1b8f03f..05bbaf69516 100644 --- a/apps/correlations/plugin/src/generated/correlation/v0alpha1/correlation_object_gen.ts +++ b/apps/correlations/plugin/src/generated/correlation/v0alpha1/correlation_object_gen.ts @@ -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; } diff --git a/apps/correlations/plugin/src/generated/correlation/v0alpha1/types.status.gen.ts b/apps/correlations/plugin/src/generated/correlation/v0alpha1/types.status.gen.ts deleted file mode 100644 index 01be8df7961..00000000000 --- a/apps/correlations/plugin/src/generated/correlation/v0alpha1/types.status.gen.ts +++ /dev/null @@ -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; -} - -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; - // additionalFields is reserved for future use - additionalFields?: Record; -} - -export const defaultStatus = (): Status => ({ -}); - diff --git a/apps/dashboard/go.mod b/apps/dashboard/go.mod index 52679f30ed5..adfae61e8d1 100644 --- a/apps/dashboard/go.mod +++ b/apps/dashboard/go.mod @@ -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 diff --git a/apps/dashboard/go.sum b/apps/dashboard/go.sum index 6dc1a3798fd..4358d7d815a 100644 --- a/apps/dashboard/go.sum +++ b/apps/dashboard/go.sum @@ -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= diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/search.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/search.go index ea0084efe2a..dffca7bc430 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/search.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/search.go @@ -4,6 +4,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" ) // +k8s:deepcopy-gen=true @@ -69,6 +70,8 @@ type DashboardHit struct { Tags []string `json:"tags,omitempty"` // The k8s name (eg, grafana UID) for the parent folder Folder string `json:"folder,omitempty"` + // The resource is managed + ManagedBy ManagedBy `json:"managedBy,omitzero,omitempty"` // Stick untyped extra fields in this object (including the sort value) Field *common.Unstructured `json:"field,omitzero,omitempty"` // When using "real" search, this is the score @@ -77,6 +80,15 @@ type DashboardHit struct { Explain *common.Unstructured `json:"explain,omitzero,omitempty"` } +type ManagedBy struct { + Kind utils.ManagerKind `json:"kind"` + ID string `json:"id,omitempty"` +} + +func (m ManagedBy) IsZero() bool { + return m.Kind == "" && m.ID == "" +} + // +k8s:deepcopy-gen=true type FacetResult struct { Field string `json:"field,omitempty"` diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.deepcopy.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.deepcopy.go index 99a44dde4e3..efdbdf07999 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.deepcopy.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.deepcopy.go @@ -76,6 +76,7 @@ func (in *DashboardHit) DeepCopyInto(out *DashboardHit) { *out = make([]string, len(*in)) copy(*out, *in) } + out.ManagedBy = in.ManagedBy if in.Field != nil { in, out := &in.Field, &out.Field *out = (*in).DeepCopy() diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go index a26bc80e5d4..0027a8b8cc4 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/zz_generated.openapi.go @@ -35,6 +35,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1.LibraryPanelList": schema_pkg_apis_dashboard_v0alpha1_LibraryPanelList(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1.LibraryPanelSpec": schema_pkg_apis_dashboard_v0alpha1_LibraryPanelSpec(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1.LibraryPanelStatus": schema_pkg_apis_dashboard_v0alpha1_LibraryPanelStatus(ref), + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1.ManagedBy": schema_pkg_apis_dashboard_v0alpha1_ManagedBy(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1.SearchResults": schema_pkg_apis_dashboard_v0alpha1_SearchResults(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1.SortBy": schema_pkg_apis_dashboard_v0alpha1_SortBy(ref), "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1.SortableField": schema_pkg_apis_dashboard_v0alpha1_SortableField(ref), @@ -337,6 +338,13 @@ func schema_pkg_apis_dashboard_v0alpha1_DashboardHit(ref common.ReferenceCallbac Format: "", }, }, + "managedBy": { + SchemaProps: spec.SchemaProps{ + Description: "The resource is managed", + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1.ManagedBy"), + }, + }, "field": { SchemaProps: spec.SchemaProps{ Description: "Stick untyped extra fields in this object (including the sort value)", @@ -361,7 +369,7 @@ func schema_pkg_apis_dashboard_v0alpha1_DashboardHit(ref common.ReferenceCallbac }, }, Dependencies: []string{ - "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"}, + "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1.ManagedBy", "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"}, } } @@ -1027,6 +1035,32 @@ func schema_pkg_apis_dashboard_v0alpha1_LibraryPanelStatus(ref common.ReferenceC } } +func schema_pkg_apis_dashboard_v0alpha1_ManagedBy(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "id": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"kind"}, + }, + }, + } +} + func schema_pkg_apis_dashboard_v0alpha1_SearchResults(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/apps/dashboard/pkg/apis/dashboard_manifest.go b/apps/dashboard/pkg/apis/dashboard_manifest.go index 1e8f261e65b..3ea4e0284b3 100644 --- a/apps/dashboard/pkg/apis/dashboard_manifest.go +++ b/apps/dashboard/pkg/apis/dashboard_manifest.go @@ -21,8 +21,9 @@ import ( ) var appManifestData = app.ManifestData{ - AppName: "dashboard", - Group: "dashboard.grafana.app", + AppName: "dashboard", + Group: "dashboard.grafana.app", + PreferredVersion: "v1beta1", Versions: []app.ManifestVersion{ { Name: "v0alpha1", diff --git a/apps/dashboard/pkg/migration/schemaversion/v16.go b/apps/dashboard/pkg/migration/schemaversion/v16.go index f360fe246ad..d8fb357c3ef 100644 --- a/apps/dashboard/pkg/migration/schemaversion/v16.go +++ b/apps/dashboard/pkg/migration/schemaversion/v16.go @@ -128,22 +128,18 @@ func upgradeToGridLayout(dashboard map[string]interface{}) { continue } - // Set default span (line 1063 in TS) - span := GetFloatValue(panel, "span", defaultPanelSpan) + // Check if panel already has gridPos but no valid span + // If span is missing or zero, and gridPos exists, preserve gridPos dimensions + var panelWidth, panelHeight int + span := GetFloatValue(panel, "span", 0) + existingGridPos, hasGridPos := panel["gridPos"].(map[string]interface{}) - // 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)) - } - } - - panelWidth := int(math.Floor(span * widthFactor)) - panelHeight := rowGridHeight - if panelHeightValue, hasHeight := panel["height"]; hasHeight { - if h, ok := ConvertToFloat(panelHeightValue); ok { - panelHeight = getGridHeight(h) - } + if hasGridPos && span == 0 { + // Panel already has gridPos but no valid span - preserve its dimensions + panelWidth = GetIntValue(existingGridPos, "w", int(defaultPanelSpan*widthFactor)) + panelHeight = GetIntValue(existingGridPos, "h", rowGridHeight) + } else { + panelWidth, panelHeight = calculatePanelDimensionsFromSpan(span, panel, widthFactor, rowGridHeight) } panelPos := rowArea.getPanelPosition(panelHeight, panelWidth) @@ -315,3 +311,27 @@ 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) { + // Set default span if still 0 + if span == 0 { + span = defaultPanelSpan + } + + 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 +} diff --git a/apps/dashboard/pkg/migration/schemaversion/v34.go b/apps/dashboard/pkg/migration/schemaversion/v34.go index 653a055d175..4d371c37d39 100644 --- a/apps/dashboard/pkg/migration/schemaversion/v34.go +++ b/apps/dashboard/pkg/migration/schemaversion/v34.go @@ -1,6 +1,9 @@ package schemaversion -import "context" +import ( + "context" + "fmt" +) // V34 migrates CloudWatch queries that use multiple statistics into separate queries. // @@ -113,9 +116,24 @@ func migrateCloudWatchQueriesInPanel(panel map[string]interface{}) { continue } - // Add CloudWatch fields if missing (set to 0 if not present) - t["metricEditorMode"] = GetIntValue(t, "metricEditorMode", 0) - t["metricQueryType"] = GetIntValue(t, "metricQueryType", 0) + // Add CloudWatch fields if missing (matches frontend migrateCloudWatchQuery logic) + if _, hasMetricQueryType := t["metricQueryType"]; !hasMetricQueryType { + t["metricQueryType"] = 0 // MetricQueryType.Search + } + + if _, hasMetricEditorMode := t["metricEditorMode"]; !hasMetricEditorMode { + metricQueryType := GetIntValue(t, "metricQueryType", 0) + if metricQueryType == 1 { // MetricQueryType.Insights + t["metricEditorMode"] = 1 // MetricEditorMode.Code + } else { + expression := GetStringValue(t, "expression") + if expression != "" { + t["metricEditorMode"] = 1 // MetricEditorMode.Code + } else { + t["metricEditorMode"] = 0 // MetricEditorMode.Builder + } + } + } // Get valid statistics (including null and empty strings) validStats, isEmpty := getValidStatistics(t["statistics"]) @@ -137,19 +155,20 @@ func migrateCloudWatchQueriesInPanel(panel map[string]interface{}) { // No valid statistics - keep query as-is newTargets = append(newTargets, t) case 1: - // Single statistic - set statistic field if not null - if statString := GetStringValue(map[string]interface{}{"stat": validStats[0]}, "stat"); statString != "" { - t["statistic"] = statString + // Single statistic - set statistic field + // Frontend doesn't set statistic property for null values + if validStats[0] != nil { + t["statistic"] = validStats[0] } newTargets = append(newTargets, t) default: // Multiple statistics - create separate queries for i, stat := range validStats { newQuery := copyMap(t) + // Set statistic field + // Frontend doesn't set statistic property for null values if stat != nil { - if statString, ok := stat.(string); ok { - newQuery["statistic"] = statString - } + newQuery["statistic"] = stat } if i == 0 { @@ -210,10 +229,11 @@ func migrateCloudWatchAnnotationQueries(dashboard map[string]interface{}) { delete(a, "statistics") annotationsList[i] = a case 1: - // Single statistic - set statistic field if not null + // Single statistic - set statistic field (matches frontend behavior) delete(a, "statistics") - if statString := GetStringValue(map[string]interface{}{"stat": validStats[0]}, "stat"); statString != "" { - a["statistic"] = statString + // Frontend doesn't set statistic property for null values + if validStats[0] != nil { + a["statistic"] = validStats[0] } annotationsList[i] = a default: @@ -221,11 +241,10 @@ func migrateCloudWatchAnnotationQueries(dashboard map[string]interface{}) { delete(a, "statistics") for j, stat := range validStats { newAnnotation := copyMap(a) - + // Set statistic field (matches frontend behavior) + // Frontend doesn't set statistic property for null values if stat != nil { - if statString, ok := stat.(string); ok { - newAnnotation["statistic"] = statString - } + newAnnotation["statistic"] = stat } // Add suffix to name @@ -260,14 +279,9 @@ func getValidStatistics(statisticsField interface{}) ([]interface{}, bool) { return nil, true // Return nil with true flag to indicate "empty array" } - var valid []interface{} - for _, stat := range statistics { - // Include null and strings (including empty strings) - if stat == nil || isString(stat) { - valid = append(valid, stat) - } - } - return valid, false + // Frontend processes ALL values in statistics array, regardless of type + // It doesn't filter out invalid types - it processes them as-is + return statistics, false } // getSuffixForStat returns the appropriate suffix for annotation names @@ -281,7 +295,20 @@ func getSuffixForStat(stat interface{}) string { } return statString } - return "" + // For non-string types, convert to string representation like JavaScript does + switch v := stat.(type) { + case map[string]interface{}: + return "[object Object]" // JavaScript behavior for objects + case []interface{}: + return "" // JavaScript behavior for arrays (empty string) + case bool: + if v { + return "true" + } + return "false" + default: + return fmt.Sprintf("%v", stat) // Numbers and other types + } } // copyMap creates a shallow copy of a map @@ -293,12 +320,6 @@ func copyMap(original map[string]interface{}) map[string]interface{} { return copy } -// isString checks if value is a string -func isString(value interface{}) bool { - _, ok := value.(string) - return ok -} - // isCloudWatchQuery checks if a query target is a CloudWatch query. func isCloudWatchQuery(target map[string]interface{}) bool { // Check for required CloudWatch query fields diff --git a/apps/dashboard/pkg/migration/schemaversion/v34_test.go b/apps/dashboard/pkg/migration/schemaversion/v34_test.go index d87b39eaefd..cc1354837e9 100644 --- a/apps/dashboard/pkg/migration/schemaversion/v34_test.go +++ b/apps/dashboard/pkg/migration/schemaversion/v34_test.go @@ -185,7 +185,7 @@ func TestV34(t *testing.T) { }, }, { - name: "handles invalid statistics in CloudWatch annotation queries", + name: "handles annotation with empty statistics array", input: map[string]interface{}{ "annotations": map[string]interface{}{ "list": []interface{}{ @@ -195,7 +195,7 @@ func TestV34(t *testing.T) { "namespace": "AWS/EC2", "region": "us-east-1", "prefixMatching": false, - "statistics": []interface{}{123, "Sum", nil}, + "statistics": []interface{}{}, }, }, }, @@ -205,15 +205,7 @@ func TestV34(t *testing.T) { "annotations": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{ - "name": "CloudWatch Annotation - Sum", - "dimensions": map[string]interface{}{"InstanceId": "i-123"}, - "namespace": "AWS/EC2", - "region": "us-east-1", - "prefixMatching": false, - "statistic": "Sum", - }, - map[string]interface{}{ - "name": "CloudWatch Annotation - null", + "name": "CloudWatch Annotation", "dimensions": map[string]interface{}{"InstanceId": "i-123"}, "namespace": "AWS/EC2", "region": "us-east-1", @@ -224,7 +216,7 @@ func TestV34(t *testing.T) { }, }, { - name: "handles multiple valid statistics mixed with invalid ones in annotation", + name: "handles annotation with single statistic", input: map[string]interface{}{ "annotations": map[string]interface{}{ "list": []interface{}{ @@ -234,7 +226,7 @@ func TestV34(t *testing.T) { "namespace": "AWS/EC2", "region": "us-east-1", "prefixMatching": false, - "statistics": []interface{}{123, "Sum", nil, "Average"}, + "statistics": []interface{}{"Average"}, }, }, }, @@ -244,22 +236,7 @@ func TestV34(t *testing.T) { "annotations": map[string]interface{}{ "list": []interface{}{ map[string]interface{}{ - "name": "CloudWatch Annotation - Sum", - "dimensions": map[string]interface{}{"InstanceId": "i-123"}, - "namespace": "AWS/EC2", - "region": "us-east-1", - "prefixMatching": false, - "statistic": "Sum", - }, - map[string]interface{}{ - "name": "CloudWatch Annotation - null", - "dimensions": map[string]interface{}{"InstanceId": "i-123"}, - "namespace": "AWS/EC2", - "region": "us-east-1", - "prefixMatching": false, - }, - map[string]interface{}{ - "name": "CloudWatch Annotation - Average", + "name": "CloudWatch Annotation", "dimensions": map[string]interface{}{"InstanceId": "i-123"}, "namespace": "AWS/EC2", "region": "us-east-1", @@ -271,7 +248,7 @@ func TestV34(t *testing.T) { }, }, { - name: "handles CloudWatch annotation with all invalid statistics", + name: "handles annotation with null statistic", input: map[string]interface{}{ "annotations": map[string]interface{}{ "list": []interface{}{ @@ -281,7 +258,7 @@ func TestV34(t *testing.T) { "namespace": "AWS/EC2", "region": "us-east-1", "prefixMatching": false, - "statistics": []interface{}{123, nil, map[string]interface{}{"invalid": "value"}}, + "statistics": []interface{}{nil}, }, }, }, @@ -296,6 +273,87 @@ func TestV34(t *testing.T) { "namespace": "AWS/EC2", "region": "us-east-1", "prefixMatching": false, + // No statistic property for null + }, + }, + }, + }, + }, + { + name: "handles annotation with all suffix types (for getSuffixForStat coverage)", + input: map[string]interface{}{ + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "CloudWatch Annotation", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "prefixMatching": false, + "statistics": []interface{}{123, true, false, map[string]interface{}{}, []interface{}{}, "", "Average"}, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": int(34), + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "CloudWatch Annotation - 123", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "prefixMatching": false, + "statistic": 123, + }, + map[string]interface{}{ + "name": "CloudWatch Annotation - true", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "prefixMatching": false, + "statistic": true, + }, + map[string]interface{}{ + "name": "CloudWatch Annotation - false", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "prefixMatching": false, + "statistic": false, + }, + map[string]interface{}{ + "name": "CloudWatch Annotation - [object Object]", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "prefixMatching": false, + "statistic": map[string]interface{}{}, + }, + map[string]interface{}{ + "name": "CloudWatch Annotation - ", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "prefixMatching": false, + "statistic": []interface{}{}, + }, + map[string]interface{}{ + "name": "CloudWatch Annotation - ", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "prefixMatching": false, + "statistic": "", + }, + map[string]interface{}{ + "name": "CloudWatch Annotation - Average", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "prefixMatching": false, + "statistic": "Average", }, }, }, @@ -468,94 +526,6 @@ func TestV34(t *testing.T) { }, }, }, - { - name: "handles invalid statistics values gracefully", - input: map[string]interface{}{ - "panels": []interface{}{ - map[string]interface{}{ - "id": 1, - "targets": []interface{}{ - map[string]interface{}{ - "refId": "A", - "dimensions": map[string]interface{}{"InstanceId": "i-123"}, - "namespace": "AWS/EC2", - "region": "us-east-1", - "metricName": "CPUUtilization", - "statistics": []interface{}{123, nil, "Average", map[string]interface{}{"invalid": "value"}}, - }, - }, - }, - }, - }, - expected: map[string]interface{}{ - "schemaVersion": int(34), - "panels": []interface{}{ - map[string]interface{}{ - "id": 1, - "targets": []interface{}{ - map[string]interface{}{ - "refId": "A", - "dimensions": map[string]interface{}{"InstanceId": "i-123"}, - "namespace": "AWS/EC2", - "region": "us-east-1", - "metricName": "CPUUtilization", - "metricEditorMode": 0, - "metricQueryType": 0, - }, - map[string]interface{}{ - "refId": "B", - "dimensions": map[string]interface{}{"InstanceId": "i-123"}, - "namespace": "AWS/EC2", - "region": "us-east-1", - "metricName": "CPUUtilization", - "statistic": "Average", - "metricEditorMode": 0, - "metricQueryType": 0, - }, - }, - }, - }, - }, - }, - { - name: "handles CloudWatch query with all invalid statistics", - input: map[string]interface{}{ - "panels": []interface{}{ - map[string]interface{}{ - "id": 1, - "targets": []interface{}{ - map[string]interface{}{ - "refId": "A", - "dimensions": map[string]interface{}{"InstanceId": "i-123"}, - "namespace": "AWS/EC2", - "region": "us-east-1", - "metricName": "CPUUtilization", - "statistics": []interface{}{123, nil, map[string]interface{}{"invalid": "value"}}, - }, - }, - }, - }, - }, - expected: map[string]interface{}{ - "schemaVersion": int(34), - "panels": []interface{}{ - map[string]interface{}{ - "id": 1, - "targets": []interface{}{ - map[string]interface{}{ - "refId": "A", - "dimensions": map[string]interface{}{"InstanceId": "i-123"}, - "namespace": "AWS/EC2", - "region": "us-east-1", - "metricName": "CPUUtilization", - "metricEditorMode": 0, - "metricQueryType": 0, - }, - }, - }, - }, - }, - }, { name: "preserves other query properties during migration", input: map[string]interface{}{ @@ -768,6 +738,88 @@ func TestV34(t *testing.T) { }, }, }, + { + name: "preserves existing metricQueryType (hasOwnProperty test)", + input: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "targets": []interface{}{ + map[string]interface{}{ + "refId": "A", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "metricQueryType": 1, // Already exists - should be preserved + "statistics": []interface{}{"Average"}, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": int(34), + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "targets": []interface{}{ + map[string]interface{}{ + "refId": "A", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "metricQueryType": 1, // Preserved + "metricEditorMode": 1, // Set to Code due to Insights + "statistic": "Average", + }, + }, + }, + }, + }, + }, + { + name: "preserves existing metricEditorMode (hasOwnProperty test)", + input: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "targets": []interface{}{ + map[string]interface{}{ + "refId": "A", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "metricEditorMode": 1, // Already exists - should be preserved + "statistics": []interface{}{"Average"}, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": int(34), + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "targets": []interface{}{ + map[string]interface{}{ + "refId": "A", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "metricEditorMode": 1, // Preserved + "metricQueryType": 0, // Set to default + "statistic": "Average", + }, + }, + }, + }, + }, + }, { name: "preserves existing metricEditorMode and metricQueryType values", input: map[string]interface{}{ @@ -826,6 +878,174 @@ func TestV34(t *testing.T) { }, }, }, + { + name: "handles refId generation with existing refIds", + input: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "targets": []interface{}{ + map[string]interface{}{"refId": "A", "expr": "prometheus"}, + map[string]interface{}{"refId": "C", "expr": "prometheus"}, // Skip B to test gap filling + map[string]interface{}{ + "refId": "D", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "statistics": []interface{}{"Average", "Maximum"}, + }, + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": int(34), + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "targets": []interface{}{ + map[string]interface{}{"refId": "A", "expr": "prometheus"}, + map[string]interface{}{"refId": "C", "expr": "prometheus"}, + map[string]interface{}{ + "refId": "D", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "statistic": "Average", + "metricEditorMode": 0, + "metricQueryType": 0, + }, + map[string]interface{}{ + "refId": "B", // Should use the available B + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "statistic": "Maximum", + "metricEditorMode": 0, + "metricQueryType": 0, + }, + }, + }, + }, + }, + }, + { + name: "handles non-CloudWatch annotation (skips migration)", + input: map[string]interface{}{ + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "Regular Annotation", + "datasource": "prometheus", + }, + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": int(34), + "annotations": map[string]interface{}{ + "list": []interface{}{ + map[string]interface{}{ + "name": "Regular Annotation", + "datasource": "prometheus", + }, + }, + }, + }, + }, + { + name: "handles missing annotations list", + input: map[string]interface{}{ + "annotations": map[string]interface{}{}, + }, + expected: map[string]interface{}{ + "schemaVersion": int(34), + "annotations": map[string]interface{}{}, + }, + }, + { + name: "handles missing annotations entirely", + input: map[string]interface{}{ + "panels": []interface{}{}, + }, + expected: map[string]interface{}{ + "schemaVersion": int(34), + "panels": []interface{}{}, + }, + }, + { + name: "tests generateNextRefId double letter generation", + input: map[string]interface{}{ + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "targets": func() []interface{} { + targets := []interface{}{} + // Create A-Z refIds + for c := 'A'; c <= 'Z'; c++ { + targets = append(targets, map[string]interface{}{ + "refId": string(c), + "expr": "prometheus", + }) + } + // Add CloudWatch query that will need double letter refId + targets = append(targets, map[string]interface{}{ + "refId": "AA", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "statistics": []interface{}{"Average", "Maximum"}, + }) + return targets + }(), + }, + }, + }, + expected: map[string]interface{}{ + "schemaVersion": int(34), + "panels": []interface{}{ + map[string]interface{}{ + "id": 1, + "targets": func() []interface{} { + targets := []interface{}{} + // Create A-Z refIds + for c := 'A'; c <= 'Z'; c++ { + targets = append(targets, map[string]interface{}{ + "refId": string(c), + "expr": "prometheus", + }) + } + // Add the migrated CloudWatch queries + targets = append(targets, map[string]interface{}{ + "refId": "AA", + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "statistic": "Average", + "metricEditorMode": 0, + "metricQueryType": 0, + }) + targets = append(targets, map[string]interface{}{ + "refId": "AB", // Next available double letter + "dimensions": map[string]interface{}{"InstanceId": "i-123"}, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "statistic": "Maximum", + "metricEditorMode": 0, + "metricQueryType": 0, + }) + return targets + }(), + }, + }, + }, + }, } runMigrationTests(t, tests, schemaversion.V34) } diff --git a/apps/dashboard/pkg/migration/testdata/input/v34.multiple_stats_cloudwatch.json b/apps/dashboard/pkg/migration/testdata/input/v34.multiple_stats_cloudwatch.json index d5b5e6228f0..b3d70bd1ac9 100644 --- a/apps/dashboard/pkg/migration/testdata/input/v34.multiple_stats_cloudwatch.json +++ b/apps/dashboard/pkg/migration/testdata/input/v34.multiple_stats_cloudwatch.json @@ -71,6 +71,40 @@ "prefixMatching": false, "statistics": ["InvalidStat", "Sum", null, "Average"] }, + { + "name": "CloudWatch Annotation with Null in Statistics", + "enable": true, + "iconColor": "orange", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "namespace": "AWS/EC2", + "region": "us-east-1", + "prefixMatching": false, + "statistics": [null, "Average", ""] + }, + { + "name": "CloudWatch Annotation Only Invalid Statistics", + "enable": true, + "iconColor": "pink", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "namespace": "AWS/EC2", + "region": "us-east-1", + "prefixMatching": false, + "statistics": [123, true, {}] + }, { "name": "Non-CloudWatch Annotation", "enable": true, @@ -348,6 +382,143 @@ { "id": 10, "type": "timeseries", + "title": "CloudWatch Query Missing Editor Fields", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "targets": [ + { + "refId": "A", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "dimensions": { + "InstanceId": "i-missing-fields" + }, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "statistics": ["Average"] + } + ] + }, + { + "id": 11, + "type": "timeseries", + "title": "CloudWatch Query with Expression (Code Mode)", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "targets": [ + { + "refId": "A", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "dimensions": { + "InstanceId": "i-with-expression" + }, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "statistics": ["Average", "Maximum"] + } + ] + }, + { + "id": 12, + "type": "timeseries", + "title": "CloudWatch Insights Query Missing Editor Mode", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "targets": [ + { + "refId": "A", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "dimensions": { + "InstanceId": "i-insights" + }, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "metricQueryType": 1, + "statistics": ["Average"] + } + ] + }, + { + "id": 13, + "type": "timeseries", + "title": "CloudWatch Query with Null Statistics", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "targets": [ + { + "refId": "A", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "statistics": [null, "Average", "", "Maximum"] + } + ] + }, + { + "id": 14, + "type": "timeseries", + "title": "CloudWatch Query with Only Invalid Statistics", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "targets": [ + { + "refId": "A", + "datasource": { + "uid": "default-ds-uid", + "type": "prometheus", + "apiVersion": "v1" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "namespace": "AWS/EC2", + "region": "us-east-1", + "metricName": "CPUUtilization", + "statistics": [123, true, {}, []] + } + ] + }, + { + "id": 15, + "type": "timeseries", "title": "Non-CloudWatch Panel", "datasource": { "uid": "prometheus" diff --git a/apps/dashboard/pkg/migration/testdata/output/latest_version/v34.multiple_stats_cloudwatch.v42.json b/apps/dashboard/pkg/migration/testdata/output/latest_version/v34.multiple_stats_cloudwatch.v42.json index ce4c9905f25..12572e9d9b9 100644 --- a/apps/dashboard/pkg/migration/testdata/output/latest_version/v34.multiple_stats_cloudwatch.v42.json +++ b/apps/dashboard/pkg/migration/testdata/output/latest_version/v34.multiple_stats_cloudwatch.v42.json @@ -80,6 +80,39 @@ "region": "us-east-1", "statistic": "InvalidStat" }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - null", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - 123", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": 123 + }, { "datasource": { "uid": "prometheus" @@ -171,6 +204,74 @@ "prefixMatching": false, "region": "us-east-1", "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - Average", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - ", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - true", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": true + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - [object Object]", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": {} } ] }, @@ -631,9 +732,274 @@ }, { "datasource": { - "uid": "prometheus" + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" }, "id": 10, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-missing-fields" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + } + ], + "title": "CloudWatch Query Missing Editor Fields", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 11, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query with Expression (Code Mode)", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 12, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-insights" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + } + ], + "title": "CloudWatch Insights Query Missing Editor Mode", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 13, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "C", + "region": "us-east-1", + "statistic": "" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "D", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query with Null Statistics", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 14, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": 123 + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": true + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "C", + "region": "us-east-1", + "statistic": {} + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "D", + "region": "us-east-1", + "statistic": [] + } + ], + "title": "CloudWatch Query with Only Invalid Statistics", + "type": "timeseries" + }, + { + "datasource": { + "uid": "prometheus" + }, + "id": 15, "targets": [ { "datasource": { diff --git a/apps/dashboard/pkg/migration/testdata/output/single_version/v34.multiple_stats_cloudwatch.v34.json b/apps/dashboard/pkg/migration/testdata/output/single_version/v34.multiple_stats_cloudwatch.v34.json index 77f9d43f0ed..85a9932902b 100644 --- a/apps/dashboard/pkg/migration/testdata/output/single_version/v34.multiple_stats_cloudwatch.v34.json +++ b/apps/dashboard/pkg/migration/testdata/output/single_version/v34.multiple_stats_cloudwatch.v34.json @@ -80,6 +80,39 @@ "region": "us-east-1", "statistic": "InvalidStat" }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - null", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - 123", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": 123 + }, { "datasource": { "uid": "prometheus" @@ -171,6 +204,74 @@ "prefixMatching": false, "region": "us-east-1", "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - Average", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-annotation" + }, + "enable": true, + "iconColor": "orange", + "name": "CloudWatch Annotation with Null in Statistics - ", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": "" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - true", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": true + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-annotation" + }, + "enable": true, + "iconColor": "pink", + "name": "CloudWatch Annotation Only Invalid Statistics - [object Object]", + "namespace": "AWS/EC2", + "prefixMatching": false, + "region": "us-east-1", + "statistic": {} } ] }, @@ -631,9 +732,274 @@ }, { "datasource": { - "uid": "prometheus" + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" }, "id": 10, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-missing-fields" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + } + ], + "title": "CloudWatch Query Missing Editor Fields", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 11, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-with-expression" + }, + "expression": "SEARCH('{AWS/EC2,InstanceId} MetricName=\"CPUUtilization\"', 'Average', 300)", + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query with Expression (Code Mode)", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 12, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-insights" + }, + "metricEditorMode": 1, + "metricName": "CPUUtilization", + "metricQueryType": 1, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": "Average" + } + ], + "title": "CloudWatch Insights Query Missing Editor Mode", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 13, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": "Average" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "C", + "region": "us-east-1", + "statistic": "" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-null-stats" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "D", + "region": "us-east-1", + "statistic": "Maximum" + } + ], + "title": "CloudWatch Query with Null Statistics", + "type": "timeseries" + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "id": 14, + "targets": [ + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "A", + "region": "us-east-1", + "statistic": 123 + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "B", + "region": "us-east-1", + "statistic": true + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "C", + "region": "us-east-1", + "statistic": {} + }, + { + "datasource": { + "apiVersion": "v1", + "type": "prometheus", + "uid": "default-ds-uid" + }, + "dimensions": { + "InstanceId": "i-invalid-only" + }, + "metricEditorMode": 0, + "metricName": "CPUUtilization", + "metricQueryType": 0, + "namespace": "AWS/EC2", + "refId": "D", + "region": "us-east-1", + "statistic": [] + } + ], + "title": "CloudWatch Query with Only Invalid Statistics", + "type": "timeseries" + }, + { + "datasource": { + "uid": "prometheus" + }, + "id": 15, "targets": [ { "datasource": { diff --git a/apps/folder/go.mod b/apps/folder/go.mod index 92db7679c96..6db44655eeb 100644 --- a/apps/folder/go.mod +++ b/apps/folder/go.mod @@ -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 diff --git a/apps/folder/go.sum b/apps/folder/go.sum index 404d42859b2..ab3cbcecbb1 100644 --- a/apps/folder/go.sum +++ b/apps/folder/go.sum @@ -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= diff --git a/apps/folder/pkg/apis/folder_manifest.go b/apps/folder/pkg/apis/folder_manifest.go index 37e994e62c6..4fcb76837ed 100644 --- a/apps/folder/pkg/apis/folder_manifest.go +++ b/apps/folder/pkg/apis/folder_manifest.go @@ -18,8 +18,9 @@ import ( ) var appManifestData = app.ManifestData{ - AppName: "folder", - Group: "folder.grafana.app", + AppName: "folder", + Group: "folder.grafana.app", + PreferredVersion: "v1beta1", Versions: []app.ManifestVersion{ { Name: "v1beta1", diff --git a/apps/iam/Makefile b/apps/iam/Makefile index 71b00aca821..c11c35f7a90 100644 --- a/apps/iam/Makefile +++ b/apps/iam/Makefile @@ -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 diff --git a/apps/iam/Tiltfile b/apps/iam/Tiltfile index ab7c530e4b6..fa3c78faaf7 100644 --- a/apps/iam/Tiltfile +++ b/apps/iam/Tiltfile @@ -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 diff --git a/apps/iam/cmd/operator/Dockerfile b/apps/iam/cmd/operator/Dockerfile deleted file mode 100644 index 513adce4f24..00000000000 --- a/apps/iam/cmd/operator/Dockerfile +++ /dev/null @@ -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"] \ No newline at end of file diff --git a/apps/iam/cmd/operator/authrt.go b/apps/iam/cmd/operator/authrt.go deleted file mode 100644 index 55d1c4cbecc..00000000000 --- a/apps/iam/cmd/operator/authrt.go +++ /dev/null @@ -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) -} diff --git a/apps/iam/cmd/operator/config.go b/apps/iam/cmd/operator/config.go deleted file mode 100644 index f5481dfb9bd..00000000000 --- a/apps/iam/cmd/operator/config.go +++ /dev/null @@ -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 -} diff --git a/apps/iam/cmd/operator/kubeconfig.go b/apps/iam/cmd/operator/kubeconfig.go deleted file mode 100644 index b704979cd9a..00000000000 --- a/apps/iam/cmd/operator/kubeconfig.go +++ /dev/null @@ -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 -} diff --git a/apps/iam/cmd/operator/main.go b/apps/iam/cmd/operator/main.go deleted file mode 100644 index 2824de11edd..00000000000 --- a/apps/iam/cmd/operator/main.go +++ /dev/null @@ -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") -} diff --git a/apps/iam/go.mod b/apps/iam/go.mod index a7ac326bd6c..b196d540f3f 100644 --- a/apps/iam/go.mod +++ b/apps/iam/go.mod @@ -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 diff --git a/apps/iam/go.sum b/apps/iam/go.sum index b561836b7fd..5cf6786bea4 100644 --- a/apps/iam/go.sum +++ b/apps/iam/go.sum @@ -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= diff --git a/apps/iam/kinds/v0alpha1/rolebindingspec.cue b/apps/iam/kinds/v0alpha1/rolebindingspec.cue index 0b4a6396f26..ef889668201 100644 --- a/apps/iam/kinds/v0alpha1/rolebindingspec.cue +++ b/apps/iam/kinds/v0alpha1/rolebindingspec.cue @@ -14,8 +14,8 @@ RoleBindingSpec: { name: string } - subjects: [...#Subject] - roleRef: #RoleRef + subject: #Subject + roleRefs: [...#RoleRef] } GlobalRoleBindingSpec: { @@ -32,6 +32,6 @@ GlobalRoleBindingSpec: { name: string } - subjects: [...#Subject] - roleRef: #RoleRef + subject: #Subject + roleRefs: [...#RoleRef] } diff --git a/apps/iam/kinds/v0alpha1/teambindingspec.cue b/apps/iam/kinds/v0alpha1/teambindingspec.cue index 3bd130c902d..c20e592e458 100644 --- a/apps/iam/kinds/v0alpha1/teambindingspec.cue +++ b/apps/iam/kinds/v0alpha1/teambindingspec.cue @@ -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:{ diff --git a/apps/iam/local/Dockerfile b/apps/iam/local/Dockerfile new file mode 100644 index 00000000000..28df71c2384 --- /dev/null +++ b/apps/iam/local/Dockerfile @@ -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"] diff --git a/apps/iam/local/yamls/operator.yaml b/apps/iam/local/yamls/operator.yaml index 574b9ad5b7d..6e17d5d670b 100644 --- a/apps/iam/local/yamls/operator.yaml +++ b/apps/iam/local/yamls/operator.yaml @@ -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: diff --git a/apps/iam/pkg/apis/iam/v0alpha1/corerole_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/corerole_client_gen.go index ae553035b02..371c97fdf4b 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/corerole_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/corerole_client_gen.go @@ -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) } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/corerole_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/corerole_object_gen.go index 4aba32da755..625cced10b9 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/corerole_object_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/corerole_object_gen.go @@ -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) -} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/globalrole_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/globalrole_client_gen.go index 71db46f40fa..1a0133a4798 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/globalrole_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/globalrole_client_gen.go @@ -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) } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/globalrole_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/globalrole_object_gen.go index 2081adb8507..27165fe70bd 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/globalrole_object_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/globalrole_object_gen.go @@ -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) -} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_client_gen.go index 66ea08e4d69..9c06311b938 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_client_gen.go @@ -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) } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_object_gen.go index 657f1830ff1..3bd4609d25d 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_object_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_object_gen.go @@ -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) -} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_spec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_spec_gen.go index a60921ecab5..ff3ff78da08 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_spec_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/globalrolebinding_spec_gen.go @@ -30,15 +30,15 @@ func NewGlobalRoleBindingspecRoleRef() *GlobalRoleBindingspecRoleRef { // +k8s:openapi-gen=true type GlobalRoleBindingSpec struct { - Subjects []GlobalRoleBindingspecSubject `json:"subjects"` - RoleRef GlobalRoleBindingspecRoleRef `json:"roleRef"` + Subject GlobalRoleBindingspecSubject `json:"subject"` + RoleRefs []GlobalRoleBindingspecRoleRef `json:"roleRefs"` } // NewGlobalRoleBindingSpec creates a new GlobalRoleBindingSpec object. func NewGlobalRoleBindingSpec() *GlobalRoleBindingSpec { return &GlobalRoleBindingSpec{ - Subjects: []GlobalRoleBindingspecSubject{}, - RoleRef: *NewGlobalRoleBindingspecRoleRef(), + Subject: *NewGlobalRoleBindingspecSubject(), + RoleRefs: []GlobalRoleBindingspecRoleRef{}, } } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/register.go b/apps/iam/pkg/apis/iam/v0alpha1/register.go index 7d313ac250b..16ee89847ef 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/register.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/register.go @@ -5,10 +5,11 @@ import ( "strings" "time" - "github.com/grafana/grafana/pkg/apimachinery/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + + "github.com/grafana/grafana/pkg/apimachinery/utils" ) const ( @@ -205,6 +206,30 @@ var TeamBindingResourceInfo = utils.NewResourceInfo( }, ) +var RoleBindingInfo = utils.NewResourceInfo(GROUP, VERSION, + "rolebindings", "rolebinding", "RoleBinding", + func() runtime.Object { return &RoleBinding{} }, + func() runtime.Object { return &RoleBindingList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + roleBinding, ok := obj.(*RoleBinding) + if ok { + if roleBinding != nil { + return []interface{}{ + roleBinding.Name, + roleBinding.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + } + } + return nil, fmt.Errorf("expected role binding") + }, + }, +) + var ( SchemeBuilder runtime.SchemeBuilder localSchemeBuilder = &SchemeBuilder @@ -237,6 +262,8 @@ func AddAuthZKnownTypes(scheme *runtime.Scheme) error { &CoreRoleList{}, &Role{}, &RoleList{}, + &RoleBinding{}, + &RoleBindingList{}, // What is this about? &metav1.PartialObjectMetadata{}, diff --git a/apps/iam/pkg/apis/iam/v0alpha1/resourcepermission_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/resourcepermission_client_gen.go index 05c70845e26..dab46ffdc05 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/resourcepermission_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/resourcepermission_client_gen.go @@ -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) } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/resourcepermission_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/resourcepermission_object_gen.go index 4fcebde9e01..996beb7e002 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/resourcepermission_object_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/resourcepermission_object_gen.go @@ -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) -} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/role_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/role_client_gen.go index e61d3fc83dd..b53bfdd9e6d 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/role_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/role_client_gen.go @@ -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) } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/role_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/role_object_gen.go index 9256a618c9f..20bb587157e 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/role_object_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/role_object_gen.go @@ -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) -} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_client_gen.go index 13e78c70adf..96807b1b795 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_client_gen.go @@ -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) } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_object_gen.go index bec75890d89..dfd7741e05a 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_object_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_object_gen.go @@ -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) -} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_spec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_spec_gen.go index cd214ca6cdd..a3684c5c741 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_spec_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/rolebinding_spec_gen.go @@ -30,15 +30,15 @@ func NewRoleBindingspecRoleRef() *RoleBindingspecRoleRef { // +k8s:openapi-gen=true type RoleBindingSpec struct { - Subjects []RoleBindingspecSubject `json:"subjects"` - RoleRef RoleBindingspecRoleRef `json:"roleRef"` + Subject RoleBindingspecSubject `json:"subject"` + RoleRefs []RoleBindingspecRoleRef `json:"roleRefs"` } // NewRoleBindingSpec creates a new RoleBindingSpec object. func NewRoleBindingSpec() *RoleBindingSpec { return &RoleBindingSpec{ - Subjects: []RoleBindingspecSubject{}, - RoleRef: *NewRoleBindingspecRoleRef(), + Subject: *NewRoleBindingspecSubject(), + RoleRefs: []RoleBindingspecRoleRef{}, } } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_client_gen.go index 3b922e8b313..0ce1616daa3 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_client_gen.go @@ -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) } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_object_gen.go index 081e244dd6e..fe3cd7f3609 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_object_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/serviceaccount_object_gen.go @@ -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) -} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/team_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/team_client_gen.go index c166c6c2881..807c68dc0f6 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/team_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/team_client_gen.go @@ -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) } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/team_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/team_object_gen.go index bfc949acfd1..4030bebb9d1 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/team_object_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/team_object_gen.go @@ -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) -} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_client_gen.go index 39729e1ece1..faaf86c1b00 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_client_gen.go @@ -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) } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_object_gen.go index 6c9f6f407e3..a958c55f5e7 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_object_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_object_gen.go @@ -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) -} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go index 0e58323533e..13abd605168 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/teambinding_spec_gen.go @@ -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"` - TeamRef TeamBindingTeamRef `json:"teamRef"` + 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{}, - TeamRef: *NewTeamBindingTeamRef(), + Subject: *NewTeamBindingspecSubject(), + TeamRef: *NewTeamBindingTeamRef(), } } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/user_client_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/user_client_gen.go index bd7af9b3361..665df84327e 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/user_client_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/user_client_gen.go @@ -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) } diff --git a/apps/iam/pkg/apis/iam/v0alpha1/user_object_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/user_object_gen.go index ce2defa33a8..373112a1d87 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/user_object_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/user_object_gen.go @@ -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) -} diff --git a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go index ac63422d531..2532b5df8cc 100644 --- a/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go +++ b/apps/iam/pkg/apis/iam/v0alpha1/zz_openapi_gen.go @@ -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"}, } } @@ -504,27 +486,27 @@ func schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingSpec(ref common.ReferenceCall SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "subjects": { + "subject": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecSubject"), + }, + }, + "roleRefs": { SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecSubject"), + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecRoleRef"), }, }, }, }, }, - "roleRef": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingspecRoleRef"), - }, - }, }, - Required: []string{"subjects", "roleRef"}, + Required: []string{"subject", "roleRefs"}, }, }, Dependencies: []string{ @@ -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"}, } } @@ -1364,27 +1328,27 @@ func schema_pkg_apis_iam_v0alpha1_RoleBindingSpec(ref common.ReferenceCallback) SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "subjects": { + "subject": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecSubject"), + }, + }, + "roleRefs": { SchemaProps: spec.SchemaProps{ Type: []string{"array"}, Items: &spec.SchemaOrArray{ Schema: &spec.Schema{ SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecSubject"), + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecRoleRef"), }, }, }, }, }, - "roleRef": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.RoleBindingspecRoleRef"), - }, - }, }, - Required: []string{"subjects", "roleRef"}, + Required: []string{"subject", "roleRefs"}, }, }, Dependencies: []string{ @@ -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,17 +2103,10 @@ func schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref common.ReferenceCallback) SchemaProps: spec.SchemaProps{ Type: []string{"object"}, Properties: map[string]spec.Schema{ - "subjects": { + "subject": { SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject"), - }, - }, - }, + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.TeamBindingspecSubject"), }, }, "teamRef": { @@ -2176,8 +2115,16 @@ func schema_pkg_apis_iam_v0alpha1_TeamBindingSpec(ref common.ReferenceCallback) 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"}, } } diff --git a/apps/iam/pkg/apis/iam_manifest.go b/apps/iam/pkg/apis/iam_manifest.go index fba76fbd7df..24aba9a1fd8 100644 --- a/apps/iam/pkg/apis/iam_manifest.go +++ b/apps/iam/pkg/apis/iam_manifest.go @@ -18,8 +18,9 @@ import ( ) var appManifestData = app.ManifestData{ - AppName: "iam", - Group: "iam.grafana.app", + AppName: "iam", + Group: "iam.grafana.app", + PreferredVersion: "v0alpha1", Versions: []app.ManifestVersion{ { Name: "v0alpha1", diff --git a/apps/iam/pkg/app/app.go b/apps/iam/pkg/app/app.go index ddaeb86b8dd..05216220d7e 100644 --- a/apps/iam/pkg/app/app.go +++ b/apps/iam/pkg/app/app.go @@ -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,12 +90,14 @@ func New(cfg app.Config) (app.App, error) { KubeConfig: cfg.KubeConfig, InformerConfig: simple.AppInformerConfig{ InformerSupplier: generateInformerSupplier(appSpecificConfig.InformerConfig, metrics), - ErrorHandler: func(ctx context.Context, err error) { - logging.FromContext(ctx).With("error", err).Error("Informer processing error") - if metrics != nil { - // Use "unknown" for action since top-level informer errors don't have specific actions - metrics.RecordReconcileFailure("unknown", "informer") - } + InformerOptions: operator.InformerOptions{ + ErrorHandler: func(ctx context.Context, err error) { + logging.FromContext(ctx).With("error", err).Error("Informer processing error") + if metrics != nil { + // Use "unknown" for action since top-level informer errors don't have specific actions + metrics.RecordReconcileFailure("unknown", "informer") + } + }, }, }, UnmanagedKinds: []simple.AppUnmanagedKind{ diff --git a/apps/iam/pkg/reconcilers/folder_reconciler.go b/apps/iam/pkg/reconcilers/folder_reconciler.go index f06b4b2e60c..59385b3b3a3 100644 --- a/apps/iam/pkg/reconcilers/folder_reconciler.go +++ b/apps/iam/pkg/reconcilers/folder_reconciler.go @@ -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 +} diff --git a/apps/iam/pkg/reconcilers/folder_service.go b/apps/iam/pkg/reconcilers/folder_service.go deleted file mode 100644 index 0a1a74a4257..00000000000 --- a/apps/iam/pkg/reconcilers/folder_service.go +++ /dev/null @@ -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 -} diff --git a/apps/iam/pkg/reconcilers/metrics.go b/apps/iam/pkg/reconcilers/metrics.go index 65a14e32df5..10802744105 100644 --- a/apps/iam/pkg/reconcilers/metrics.go +++ b/apps/iam/pkg/reconcilers/metrics.go @@ -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", }, } diff --git a/apps/investigations/go.mod b/apps/investigations/go.mod index 86bec16425b..1652941eba8 100644 --- a/apps/investigations/go.mod +++ b/apps/investigations/go.mod @@ -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 diff --git a/apps/investigations/go.sum b/apps/investigations/go.sum index b34bb79ec13..dadd72070fd 100644 --- a/apps/investigations/go.sum +++ b/apps/investigations/go.sum @@ -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= diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_client_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_client_gen.go index 5a615356172..dc73a0301e9 100644 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_client_gen.go +++ b/apps/investigations/pkg/apis/investigations/v0alpha1/investigation_client_gen.go @@ -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{ diff --git a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_client_gen.go b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_client_gen.go index 3b63abc5d09..573d743b3cf 100644 --- a/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_client_gen.go +++ b/apps/investigations/pkg/apis/investigations/v0alpha1/investigationindex_client_gen.go @@ -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{ diff --git a/apps/investigations/pkg/apis/investigations_manifest.go b/apps/investigations/pkg/apis/investigations_manifest.go index e4d0dc6fbd9..04c52dbd9ac 100644 --- a/apps/investigations/pkg/apis/investigations_manifest.go +++ b/apps/investigations/pkg/apis/investigations_manifest.go @@ -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" ) @@ -27,8 +28,9 @@ var ( ) var appManifestData = app.ManifestData{ - AppName: "investigations", - Group: "investigations.grafana.app", + 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 "/" 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) +} diff --git a/apps/investigations/pkg/app/investigations_app.go b/apps/investigations/pkg/app/investigations_app.go index 3155eb88404..516d278788d 100644 --- a/apps/investigations/pkg/app/investigations_app.go +++ b/apps/investigations/pkg/app/investigations_app.go @@ -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,8 +19,10 @@ func New(cfg app.Config) (app.App, error) { Name: "investigation", KubeConfig: cfg.KubeConfig, InformerConfig: simple.AppInformerConfig{ - ErrorHandler: func(_ context.Context, err error) { - klog.ErrorS(err, "Informer processing error") + InformerOptions: operator.InformerOptions{ + ErrorHandler: func(_ context.Context, err error) { + klog.ErrorS(err, "Informer processing error") + }, }, }, ManagedKinds: []simple.AppManagedKind{ diff --git a/apps/playlist/go.mod b/apps/playlist/go.mod index 2284cb1cb03..4419785540a 100644 --- a/apps/playlist/go.mod +++ b/apps/playlist/go.mod @@ -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 diff --git a/apps/playlist/go.sum b/apps/playlist/go.sum index b34bb79ec13..dadd72070fd 100644 --- a/apps/playlist/go.sum +++ b/apps/playlist/go.sum @@ -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= diff --git a/apps/playlist/pkg/apis/playlist_manifest.go b/apps/playlist/pkg/apis/playlist_manifest.go index f27cf6dd635..1baede5a7a4 100644 --- a/apps/playlist/pkg/apis/playlist_manifest.go +++ b/apps/playlist/pkg/apis/playlist_manifest.go @@ -25,8 +25,9 @@ var ( ) var appManifestData = app.ManifestData{ - AppName: "playlist", - Group: "playlist.grafana.app", + AppName: "playlist", + Group: "playlist.grafana.app", + PreferredVersion: "v0alpha1", Versions: []app.ManifestVersion{ { Name: "v0alpha1", diff --git a/apps/playlist/pkg/app/app.go b/apps/playlist/pkg/app/app.go index 11cb921dba9..6c9f1762a22 100644 --- a/apps/playlist/pkg/app/app.go +++ b/apps/playlist/pkg/app/app.go @@ -50,8 +50,10 @@ func New(cfg app.Config) (app.App, error) { Name: "playlist", KubeConfig: cfg.KubeConfig, InformerConfig: simple.AppInformerConfig{ - ErrorHandler: func(ctx context.Context, err error) { - klog.ErrorS(err, "Informer processing error") + InformerOptions: operator.InformerOptions{ + ErrorHandler: func(ctx context.Context, err error) { + klog.ErrorS(err, "Informer processing error") + }, }, }, ManagedKinds: []simple.AppManagedKind{ diff --git a/apps/plugins/go.mod b/apps/plugins/go.mod index 05f0880b356..35ed57d995b 100644 --- a/apps/plugins/go.mod +++ b/apps/plugins/go.mod @@ -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 diff --git a/apps/plugins/go.sum b/apps/plugins/go.sum index 19b005ae46f..ca929862af5 100644 --- a/apps/plugins/go.sum +++ b/apps/plugins/go.sum @@ -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= diff --git a/apps/plugins/pkg/apis/plugins_manifest.go b/apps/plugins/pkg/apis/plugins_manifest.go index 2ebfb699452..3965a906139 100644 --- a/apps/plugins/pkg/apis/plugins_manifest.go +++ b/apps/plugins/pkg/apis/plugins_manifest.go @@ -28,8 +28,9 @@ var ( ) var appManifestData = app.ManifestData{ - AppName: "plugins", - Group: "plugins.grafana.app", + AppName: "plugins", + Group: "plugins.grafana.app", + PreferredVersion: "v0alpha1", Versions: []app.ManifestVersion{ { Name: "v0alpha1", diff --git a/apps/plugins/pkg/app/app.go b/apps/plugins/pkg/app/app.go index 99fc20a8d2c..5503126704b 100644 --- a/apps/plugins/pkg/app/app.go +++ b/apps/plugins/pkg/app/app.go @@ -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,8 +27,10 @@ func New(cfg app.Config) (app.App, error) { Name: "plugins", KubeConfig: cfg.KubeConfig, InformerConfig: simple.AppInformerConfig{ - ErrorHandler: func(ctx context.Context, err error) { - klog.ErrorS(err, "Informer processing error") + InformerOptions: operator.InformerOptions{ + ErrorHandler: func(ctx context.Context, err error) { + klog.ErrorS(err, "Informer processing error") + }, }, }, ManagedKinds: managedKinds, diff --git a/apps/preferences/Makefile b/apps/preferences/Makefile index 230bfd4149a..bc8d6d30cb5 100644 --- a/apps/preferences/Makefile +++ b/apps/preferences/Makefile @@ -6,4 +6,5 @@ generate: install-app-sdk update-app-sdk --source=./kinds/ \ --gogenpath=./pkg/apis \ --grouping=group \ + --genoperatorstate=false \ --defencoding=none \ No newline at end of file diff --git a/apps/preferences/go.mod b/apps/preferences/go.mod index 3c369fbbfc0..f21e6a19526 100644 --- a/apps/preferences/go.mod +++ b/apps/preferences/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/preferences 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-20250804150913-990f1c69ecc2 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 diff --git a/apps/preferences/go.sum b/apps/preferences/go.sum index 392dfb7d257..112d7cb18ab 100644 --- a/apps/preferences/go.sum +++ b/apps/preferences/go.sum @@ -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-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM= @@ -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= diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_client_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_client_gen.go index 380bafb16bf..66e661e574d 100644 --- a/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_client_gen.go +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_client_gen.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type PreferencesClient struct { @@ -76,24 +75,6 @@ func (c *PreferencesClient) Patch(ctx context.Context, identifier resource.Ident return c.client.Patch(ctx, identifier, req, opts) } -func (c *PreferencesClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus PreferencesStatus, opts resource.UpdateOptions) (*Preferences, error) { - return c.client.Update(ctx, &Preferences{ - TypeMeta: metav1.TypeMeta{ - Kind: PreferencesKind().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 *PreferencesClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { return c.client.Delete(ctx, identifier, opts) } diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_object_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_object_gen.go index 9f1857a5f7b..81a4b4351ab 100644 --- a/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_object_gen.go +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_object_gen.go @@ -21,8 +21,6 @@ type Preferences struct { // Spec is the spec of the Preferences Spec PreferencesSpec `json:"spec" yaml:"spec"` - - Status PreferencesStatus `json:"status" yaml:"status"` } func (o *Preferences) GetSpec() any { @@ -39,15 +37,11 @@ func (o *Preferences) SetSpec(spec any) error { } func (o *Preferences) GetSubresources() map[string]any { - return map[string]any{ - "status": o.Status, - } + return map[string]any{} } func (o *Preferences) GetSubresource(name string) (any, bool) { switch name { - case "status": - return o.Status, true default: return nil, false } @@ -55,13 +49,6 @@ func (o *Preferences) GetSubresource(name string) (any, bool) { func (o *Preferences) SetSubresource(name string, value any) error { switch name { - case "status": - cast, ok := value.(PreferencesStatus) - if !ok { - return fmt.Errorf("cannot set status type %#v, not of type PreferencesStatus", value) - } - o.Status = cast - return nil default: return fmt.Errorf("subresource '%s' does not exist", name) } @@ -233,7 +220,6 @@ func (o *Preferences) DeepCopyInto(dst *Preferences) { 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 *PreferencesSpec) DeepCopy() *PreferencesSpec { func (s *PreferencesSpec) DeepCopyInto(dst *PreferencesSpec) { resource.CopyObjectInto(dst, s) } - -// DeepCopy creates a full deep copy of PreferencesStatus -func (s *PreferencesStatus) DeepCopy() *PreferencesStatus { - cpy := &PreferencesStatus{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies PreferencesStatus into another PreferencesStatus object -func (s *PreferencesStatus) DeepCopyInto(dst *PreferencesStatus) { - resource.CopyObjectInto(dst, s) -} diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_status_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_status_gen.go deleted file mode 100644 index 420dd054d3d..00000000000 --- a/apps/preferences/pkg/apis/preferences/v1alpha1/preferences_status_gen.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v1alpha1 - -// +k8s:openapi-gen=true -type PreferencesstatusOperatorState 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 PreferencesStatusOperatorStateState `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"` -} - -// NewPreferencesstatusOperatorState creates a new PreferencesstatusOperatorState object. -func NewPreferencesstatusOperatorState() *PreferencesstatusOperatorState { - return &PreferencesstatusOperatorState{} -} - -// +k8s:openapi-gen=true -type PreferencesStatus 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]PreferencesstatusOperatorState `json:"operatorStates,omitempty"` - // additionalFields is reserved for future use - AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` -} - -// NewPreferencesStatus creates a new PreferencesStatus object. -func NewPreferencesStatus() *PreferencesStatus { - return &PreferencesStatus{} -} - -// +k8s:openapi-gen=true -type PreferencesStatusOperatorStateState string - -const ( - PreferencesStatusOperatorStateStateSuccess PreferencesStatusOperatorStateState = "success" - PreferencesStatusOperatorStateStateInProgress PreferencesStatusOperatorStateState = "in_progress" - PreferencesStatusOperatorStateStateFailed PreferencesStatusOperatorStateState = "failed" -) diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/stars_client_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/stars_client_gen.go index 3a607012db3..9f2c56ea59b 100644 --- a/apps/preferences/pkg/apis/preferences/v1alpha1/stars_client_gen.go +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/stars_client_gen.go @@ -4,7 +4,6 @@ import ( "context" "github.com/grafana/grafana-app-sdk/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) type StarsClient struct { @@ -76,24 +75,6 @@ func (c *StarsClient) Patch(ctx context.Context, identifier resource.Identifier, return c.client.Patch(ctx, identifier, req, opts) } -func (c *StarsClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus StarsStatus, opts resource.UpdateOptions) (*Stars, error) { - return c.client.Update(ctx, &Stars{ - TypeMeta: metav1.TypeMeta{ - Kind: StarsKind().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 *StarsClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { return c.client.Delete(ctx, identifier, opts) } diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/stars_object_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/stars_object_gen.go index d6836cea565..8381844dceb 100644 --- a/apps/preferences/pkg/apis/preferences/v1alpha1/stars_object_gen.go +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/stars_object_gen.go @@ -21,8 +21,6 @@ type Stars struct { // Spec is the spec of the Stars Spec StarsSpec `json:"spec" yaml:"spec"` - - Status StarsStatus `json:"status" yaml:"status"` } func (o *Stars) GetSpec() any { @@ -39,15 +37,11 @@ func (o *Stars) SetSpec(spec any) error { } func (o *Stars) GetSubresources() map[string]any { - return map[string]any{ - "status": o.Status, - } + return map[string]any{} } func (o *Stars) GetSubresource(name string) (any, bool) { switch name { - case "status": - return o.Status, true default: return nil, false } @@ -55,13 +49,6 @@ func (o *Stars) GetSubresource(name string) (any, bool) { func (o *Stars) SetSubresource(name string, value any) error { switch name { - case "status": - cast, ok := value.(StarsStatus) - if !ok { - return fmt.Errorf("cannot set status type %#v, not of type StarsStatus", value) - } - o.Status = cast - return nil default: return fmt.Errorf("subresource '%s' does not exist", name) } @@ -233,7 +220,6 @@ func (o *Stars) DeepCopyInto(dst *Stars) { 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 *StarsSpec) DeepCopy() *StarsSpec { func (s *StarsSpec) DeepCopyInto(dst *StarsSpec) { resource.CopyObjectInto(dst, s) } - -// DeepCopy creates a full deep copy of StarsStatus -func (s *StarsStatus) DeepCopy() *StarsStatus { - cpy := &StarsStatus{} - s.DeepCopyInto(cpy) - return cpy -} - -// DeepCopyInto deep copies StarsStatus into another StarsStatus object -func (s *StarsStatus) DeepCopyInto(dst *StarsStatus) { - resource.CopyObjectInto(dst, s) -} diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/stars_status_gen.go b/apps/preferences/pkg/apis/preferences/v1alpha1/stars_status_gen.go deleted file mode 100644 index c8c7c2b07f2..00000000000 --- a/apps/preferences/pkg/apis/preferences/v1alpha1/stars_status_gen.go +++ /dev/null @@ -1,44 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. - -package v1alpha1 - -// +k8s:openapi-gen=true -type StarsstatusOperatorState 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 StarsStatusOperatorStateState `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"` -} - -// NewStarsstatusOperatorState creates a new StarsstatusOperatorState object. -func NewStarsstatusOperatorState() *StarsstatusOperatorState { - return &StarsstatusOperatorState{} -} - -// +k8s:openapi-gen=true -type StarsStatus 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]StarsstatusOperatorState `json:"operatorStates,omitempty"` - // additionalFields is reserved for future use - AdditionalFields map[string]interface{} `json:"additionalFields,omitempty"` -} - -// NewStarsStatus creates a new StarsStatus object. -func NewStarsStatus() *StarsStatus { - return &StarsStatus{} -} - -// +k8s:openapi-gen=true -type StarsStatusOperatorStateState string - -const ( - StarsStatusOperatorStateStateSuccess StarsStatusOperatorStateState = "success" - StarsStatusOperatorStateStateInProgress StarsStatusOperatorStateState = "in_progress" - StarsStatusOperatorStateStateFailed StarsStatusOperatorStateState = "failed" -) diff --git a/apps/preferences/pkg/apis/preferences/v1alpha1/zz_generated.openapi.go b/apps/preferences/pkg/apis/preferences/v1alpha1/zz_generated.openapi.go index 7b9014a57bb..c8700a3dfa5 100644 --- a/apps/preferences/pkg/apis/preferences/v1alpha1/zz_generated.openapi.go +++ b/apps/preferences/pkg/apis/preferences/v1alpha1/zz_generated.openapi.go @@ -20,14 +20,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesNavbarPreference": schema_pkg_apis_preferences_v1alpha1_PreferencesNavbarPreference(ref), "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesQueryHistoryPreference": schema_pkg_apis_preferences_v1alpha1_PreferencesQueryHistoryPreference(ref), "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesSpec": schema_pkg_apis_preferences_v1alpha1_PreferencesSpec(ref), - "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesStatus": schema_pkg_apis_preferences_v1alpha1_PreferencesStatus(ref), - "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesstatusOperatorState": schema_pkg_apis_preferences_v1alpha1_PreferencesstatusOperatorState(ref), "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.Stars": schema_pkg_apis_preferences_v1alpha1_Stars(ref), "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsList": schema_pkg_apis_preferences_v1alpha1_StarsList(ref), "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsResource": schema_pkg_apis_preferences_v1alpha1_StarsResource(ref), "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsSpec": schema_pkg_apis_preferences_v1alpha1_StarsSpec(ref), - "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsStatus": schema_pkg_apis_preferences_v1alpha1_StarsStatus(ref), - "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsstatusOperatorState": schema_pkg_apis_preferences_v1alpha1_StarsstatusOperatorState(ref), } } @@ -64,18 +60,12 @@ func schema_pkg_apis_preferences_v1alpha1_Preferences(ref common.ReferenceCallba Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesSpec"), }, }, - "status": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesStatus"), - }, - }, }, - Required: []string{"metadata", "spec", "status"}, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesSpec", "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -277,101 +267,6 @@ func schema_pkg_apis_preferences_v1alpha1_PreferencesSpec(ref common.ReferenceCa } } -func schema_pkg_apis_preferences_v1alpha1_PreferencesStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "operatorStates": { - SchemaProps: spec.SchemaProps{ - Description: "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.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesstatusOperatorState"), - }, - }, - }, - }, - }, - "additionalFields": { - SchemaProps: spec.SchemaProps{ - Description: "additionalFields is reserved for future use", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.PreferencesstatusOperatorState"}, - } -} - -func schema_pkg_apis_preferences_v1alpha1_PreferencesstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "lastEvaluation": { - SchemaProps: spec.SchemaProps{ - Description: "lastEvaluation is the ResourceVersion last evaluated", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "state": { - SchemaProps: spec.SchemaProps{ - Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "descriptiveState": { - SchemaProps: spec.SchemaProps{ - Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", - Type: []string{"string"}, - Format: "", - }, - }, - "details": { - SchemaProps: spec.SchemaProps{ - Description: "details contains any extra information that is operator-specific", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - Required: []string{"lastEvaluation", "state"}, - }, - }, - } -} - func schema_pkg_apis_preferences_v1alpha1_Stars(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -405,18 +300,12 @@ func schema_pkg_apis_preferences_v1alpha1_Stars(ref common.ReferenceCallback) co Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsSpec"), }, }, - "status": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsStatus"), - }, - }, }, - Required: []string{"metadata", "spec", "status"}, + Required: []string{"metadata", "spec"}, }, }, Dependencies: []string{ - "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsSpec", "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } @@ -542,98 +431,3 @@ func schema_pkg_apis_preferences_v1alpha1_StarsSpec(ref common.ReferenceCallback "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsResource"}, } } - -func schema_pkg_apis_preferences_v1alpha1_StarsStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "operatorStates": { - SchemaProps: spec.SchemaProps{ - Description: "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.", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsstatusOperatorState"), - }, - }, - }, - }, - }, - "additionalFields": { - SchemaProps: spec.SchemaProps{ - Description: "additionalFields is reserved for future use", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.StarsstatusOperatorState"}, - } -} - -func schema_pkg_apis_preferences_v1alpha1_StarsstatusOperatorState(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "lastEvaluation": { - SchemaProps: spec.SchemaProps{ - Description: "lastEvaluation is the ResourceVersion last evaluated", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "state": { - SchemaProps: spec.SchemaProps{ - Description: "state describes the state of the lastEvaluation. It is limited to three possible states for machine evaluation.", - Default: "", - Type: []string{"string"}, - Format: "", - }, - }, - "descriptiveState": { - SchemaProps: spec.SchemaProps{ - Description: "descriptiveState is an optional more descriptive state field which has no requirements on format", - Type: []string{"string"}, - Format: "", - }, - }, - "details": { - SchemaProps: spec.SchemaProps{ - Description: "details contains any extra information that is operator-specific", - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - Format: "", - }, - }, - }, - }, - }, - }, - Required: []string{"lastEvaluation", "state"}, - }, - }, - } -} diff --git a/apps/preferences/pkg/apis/preferences_manifest.go b/apps/preferences/pkg/apis/preferences_manifest.go index b0a3468058a..3912bc12866 100644 --- a/apps/preferences/pkg/apis/preferences_manifest.go +++ b/apps/preferences/pkg/apis/preferences_manifest.go @@ -19,17 +19,18 @@ import ( ) var ( - rawSchemaPreferencesv1alpha1 = []byte(`{"CookiePreferences":{"additionalProperties":false,"properties":{"analytics":{"additionalProperties":{},"type":"object"},"functional":{"additionalProperties":{},"type":"object"},"performance":{"additionalProperties":{},"type":"object"}},"type":"object"},"NavbarPreference":{"additionalProperties":false,"properties":{"bookmarkUrls":{"items":{"type":"string"},"type":"array"}},"required":["bookmarkUrls"],"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"},"Preferences":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"QueryHistoryPreference":{"additionalProperties":false,"properties":{"homeTab":{"description":"one of: '' | 'query' | 'starred';","type":"string"}},"type":"object"},"spec":{"additionalProperties":false,"properties":{"cookiePreferences":{"$ref":"#/components/schemas/CookiePreferences","description":"Cookie preferences"},"homeDashboardUID":{"description":"UID for the home dashboard","type":"string"},"language":{"description":"Selected language (beta)","type":"string"},"navbar":{"$ref":"#/components/schemas/NavbarPreference","description":"Navigation preferences"},"queryHistory":{"$ref":"#/components/schemas/QueryHistoryPreference","description":"Explore query history preferences"},"regionalFormat":{"description":"Selected locale (beta)","type":"string"},"theme":{"description":"light, dark, empty is default","type":"string"},"timezone":{"description":"The timezone selection\nTODO: this should use the timezone defined in common","type":"string"},"weekStart":{"description":"day of the week (sunday, monday, etc)","type":"string"}},"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"}}`) + rawSchemaPreferencesv1alpha1 = []byte(`{"CookiePreferences":{"additionalProperties":false,"properties":{"analytics":{"additionalProperties":{},"type":"object"},"functional":{"additionalProperties":{},"type":"object"},"performance":{"additionalProperties":{},"type":"object"}},"type":"object"},"NavbarPreference":{"additionalProperties":false,"properties":{"bookmarkUrls":{"items":{"type":"string"},"type":"array"}},"required":["bookmarkUrls"],"type":"object"},"Preferences":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"QueryHistoryPreference":{"additionalProperties":false,"properties":{"homeTab":{"description":"one of: '' | 'query' | 'starred';","type":"string"}},"type":"object"},"spec":{"additionalProperties":false,"properties":{"cookiePreferences":{"$ref":"#/components/schemas/CookiePreferences","description":"Cookie preferences"},"homeDashboardUID":{"description":"UID for the home dashboard","type":"string"},"language":{"description":"Selected language (beta)","type":"string"},"navbar":{"$ref":"#/components/schemas/NavbarPreference","description":"Navigation preferences"},"queryHistory":{"$ref":"#/components/schemas/QueryHistoryPreference","description":"Explore query history preferences"},"regionalFormat":{"description":"Selected locale (beta)","type":"string"},"theme":{"description":"light, dark, empty is default","type":"string"},"timezone":{"description":"The timezone selection\nTODO: this should use the timezone defined in common","type":"string"},"weekStart":{"description":"day of the week (sunday, monday, etc)","type":"string"}},"type":"object"}}`) versionSchemaPreferencesv1alpha1 app.VersionSchema _ = json.Unmarshal(rawSchemaPreferencesv1alpha1, &versionSchemaPreferencesv1alpha1) - rawSchemaStarsv1alpha1 = []byte(`{"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"},"Resource":{"additionalProperties":false,"properties":{"group":{"type":"string"},"kind":{"type":"string"},"names":{"description":"The set of resources\n+listType=set","items":{"type":"string"},"type":"array"}},"required":["group","kind","names"],"type":"object"},"Stars":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"resource":{"items":{"$ref":"#/components/schemas/Resource"},"type":"array"}},"required":["resource"],"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"}}`) + rawSchemaStarsv1alpha1 = []byte(`{"Resource":{"additionalProperties":false,"properties":{"group":{"type":"string"},"kind":{"type":"string"},"names":{"description":"The set of resources\n+listType=set","items":{"type":"string"},"type":"array"}},"required":["group","kind","names"],"type":"object"},"Stars":{"properties":{"spec":{"$ref":"#/components/schemas/spec"}},"required":["spec"]},"spec":{"additionalProperties":false,"properties":{"resource":{"items":{"$ref":"#/components/schemas/Resource"},"type":"array"}},"required":["resource"],"type":"object"}}`) versionSchemaStarsv1alpha1 app.VersionSchema _ = json.Unmarshal(rawSchemaStarsv1alpha1, &versionSchemaStarsv1alpha1) ) var appManifestData = app.ManifestData{ - AppName: "preferences", - Group: "preferences.grafana.app", + AppName: "preferences", + Group: "preferences.grafana.app", + PreferredVersion: "v1alpha1", Versions: []app.ManifestVersion{ { Name: "v1alpha1", diff --git a/apps/provisioning/Makefile b/apps/provisioning/Makefile index c3d7e748603..e9a8b1fbdf9 100644 --- a/apps/provisioning/Makefile +++ b/apps/provisioning/Makefile @@ -1,8 +1,13 @@ include ../sdk.mk -.PHONY: generate +.PHONY: generate # Run Grafana App SDK code generation generate: install-app-sdk update-app-sdk - @$(APP_SDK_BIN) generate -g ./kinds --grouping=group --postprocess --defencoding=none --useoldmanifestkinds + @$(APP_SDK_BIN) generate \ + --source=./kinds/ \ + --gogenpath=./pkg/apis \ + --grouping=group \ + --genoperatorstate=false \ + --defencoding=none .PHONY: build build: generate diff --git a/apps/provisioning/go.mod b/apps/provisioning/go.mod index 09f7cc24835..9a3cd99c066 100644 --- a/apps/provisioning/go.mod +++ b/apps/provisioning/go.mod @@ -5,7 +5,7 @@ go 1.24.6 require ( github.com/google/go-github/v70 v70.0.0 github.com/google/uuid v1.6.0 - github.com/grafana/authlib v0.0.0-20250924100039-ea07223cdb6c + github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f github.com/grafana/grafana-app-sdk/logging v0.45.0 github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 @@ -25,7 +25,7 @@ require ( github.com/blang/semver/v4 v4.0.0 // 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/go-jose/go-jose/v3 v3.0.4 // indirect github.com/go-jose/go-jose/v4 v4.1.2 // indirect @@ -40,9 +40,9 @@ require ( github.com/google/go-github/v64 v64.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/gorilla/mux v1.8.1 // indirect - github.com/grafana/authlib/types v0.0.0-20250917093142-83a502239781 // indirect + github.com/grafana/authlib/types v0.0.0-20250926065801-df98203cff37 // indirect github.com/grafana/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect - github.com/grafana/grafana-app-sdk v0.45.0 // indirect + github.com/grafana/grafana-app-sdk v0.46.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect @@ -75,7 +75,7 @@ require ( golang.org/x/time v0.13.0 // 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 diff --git a/apps/provisioning/go.sum b/apps/provisioning/go.sum index ef6ed2203e6..93819002c42 100644 --- a/apps/provisioning/go.sum +++ b/apps/provisioning/go.sum @@ -8,8 +8,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-jose/go-jose/v3 v3.0.4 h1:Wp5HA7bLQcKnf6YYao/4kpRpVMp/yf6+pJKV8WFSaNY= @@ -52,14 +52,14 @@ 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/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/apps/secret v0.0.0-20250902093454-b56b7add012f h1:f+Z5Xpfp1WNYjUe23ginerWsHWUsRgOWrr3WGu3SlWs= @@ -224,8 +224,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= diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go index 847da88fbc5..b96fb1a6d27 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/jobs.go @@ -221,9 +221,9 @@ func (in JobStatus) ToSyncStatus(jobId string) SyncStatus { } type JobResourceSummary struct { - Group string `json:"group,omitempty"` - Resource string `json:"resource,omitempty"` - Total int64 `json:"total,omitempty"` // the count (if known) + Group string `json:"group,omitempty"` + Kind string `json:"kind,omitempty"` + Total int64 `json:"total,omitempty"` // the count (if known) Create int64 `json:"create,omitempty"` Update int64 `json:"update,omitempty"` diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go index fc24ac84869..dff7c3813e5 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/types.go @@ -310,6 +310,9 @@ type RepositoryStatus struct { // Webhook Information (if applicable) Webhook *WebhookStatus `json:"webhook"` + + // Error information during repository deletion (if any) + DeleteError string `json:"deleteError,omitempty"` } // HealthFailureType represents different types of repository failures diff --git a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go index e68b03458ac..18c385ce59c 100644 --- a/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go +++ b/apps/provisioning/pkg/apis/provisioning/v0alpha1/zz_generated.openapi.go @@ -846,7 +846,7 @@ func schema_pkg_apis_provisioning_v0alpha1_JobResourceSummary(ref common.Referen Format: "", }, }, - "resource": { + "kind": { SchemaProps: spec.SchemaProps{ Type: []string{"string"}, Format: "", @@ -1587,6 +1587,13 @@ func schema_pkg_apis_provisioning_v0alpha1_RepositoryStatus(ref common.Reference Ref: ref("github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1.WebhookStatus"), }, }, + "deleteError": { + SchemaProps: spec.SchemaProps{ + Description: "Error information during repository deletion (if any)", + Type: []string{"string"}, + Format: "", + }, + }, }, Required: []string{"observedGeneration", "health", "sync", "webhook"}, }, diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobresourcesummary.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobresourcesummary.go index 75b69663794..ed6a62f651a 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobresourcesummary.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/jobresourcesummary.go @@ -7,16 +7,16 @@ package v0alpha1 // JobResourceSummaryApplyConfiguration represents a declarative configuration of the JobResourceSummary type for use // with apply. type JobResourceSummaryApplyConfiguration struct { - Group *string `json:"group,omitempty"` - Resource *string `json:"resource,omitempty"` - Total *int64 `json:"total,omitempty"` - Create *int64 `json:"create,omitempty"` - Update *int64 `json:"update,omitempty"` - Delete *int64 `json:"delete,omitempty"` - Write *int64 `json:"write,omitempty"` - Error *int64 `json:"error,omitempty"` - Noop *int64 `json:"noop,omitempty"` - Errors []string `json:"errors,omitempty"` + Group *string `json:"group,omitempty"` + Kind *string `json:"kind,omitempty"` + Total *int64 `json:"total,omitempty"` + Create *int64 `json:"create,omitempty"` + Update *int64 `json:"update,omitempty"` + Delete *int64 `json:"delete,omitempty"` + Write *int64 `json:"write,omitempty"` + Error *int64 `json:"error,omitempty"` + Noop *int64 `json:"noop,omitempty"` + Errors []string `json:"errors,omitempty"` } // JobResourceSummaryApplyConfiguration constructs a declarative configuration of the JobResourceSummary type for use with @@ -33,11 +33,11 @@ func (b *JobResourceSummaryApplyConfiguration) WithGroup(value string) *JobResou return b } -// WithResource sets the Resource field in the declarative configuration to the given value +// WithKind sets the Kind field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the Resource field is set to the value of the last call. -func (b *JobResourceSummaryApplyConfiguration) WithResource(value string) *JobResourceSummaryApplyConfiguration { - b.Resource = &value +// If called multiple times, the Kind field is set to the value of the last call. +func (b *JobResourceSummaryApplyConfiguration) WithKind(value string) *JobResourceSummaryApplyConfiguration { + b.Kind = &value return b } diff --git a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositorystatus.go b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositorystatus.go index aebb18acafd..b1f57147335 100644 --- a/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositorystatus.go +++ b/apps/provisioning/pkg/generated/applyconfiguration/provisioning/v0alpha1/repositorystatus.go @@ -12,6 +12,7 @@ type RepositoryStatusApplyConfiguration struct { Sync *SyncStatusApplyConfiguration `json:"sync,omitempty"` Stats []ResourceCountApplyConfiguration `json:"stats,omitempty"` Webhook *WebhookStatusApplyConfiguration `json:"webhook,omitempty"` + DeleteError *string `json:"deleteError,omitempty"` } // RepositoryStatusApplyConfiguration constructs a declarative configuration of the RepositoryStatus type for use with @@ -64,3 +65,11 @@ func (b *RepositoryStatusApplyConfiguration) WithWebhook(value *WebhookStatusApp b.Webhook = value return b } + +// WithDeleteError sets the DeleteError field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the DeleteError field is set to the value of the last call. +func (b *RepositoryStatusApplyConfiguration) WithDeleteError(value string) *RepositoryStatusApplyConfiguration { + b.DeleteError = &value + return b +} diff --git a/apps/provisioning/pkg/repository/factory.go b/apps/provisioning/pkg/repository/factory.go index 4220bc32adc..537c51fd5cc 100644 --- a/apps/provisioning/pkg/repository/factory.go +++ b/apps/provisioning/pkg/repository/factory.go @@ -18,7 +18,7 @@ type Extra interface { Mutate(ctx context.Context, obj runtime.Object) error } -//go:generate mockery --name=Factor --structname=MockFactory --inpackage --filename=factory_mock.go --with-expecter +//go:generate mockery --name=Factory --structname=MockFactory --inpackage --filename=factory_mock.go --with-expecter type Factory interface { Types() []provisioning.RepositoryType Build(ctx context.Context, r *provisioning.Repository) (Repository, error) diff --git a/apps/provisioning/pkg/repository/factory_mock.go b/apps/provisioning/pkg/repository/factory_mock.go new file mode 100644 index 00000000000..c6016d61bf1 --- /dev/null +++ b/apps/provisioning/pkg/repository/factory_mock.go @@ -0,0 +1,192 @@ +// Code generated by mockery v2.53.4. DO NOT EDIT. + +package repository + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + runtime "k8s.io/apimachinery/pkg/runtime" + + v0alpha1 "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +// MockFactory is an autogenerated mock type for the Factory type +type MockFactory struct { + mock.Mock +} + +type MockFactory_Expecter struct { + mock *mock.Mock +} + +func (_m *MockFactory) EXPECT() *MockFactory_Expecter { + return &MockFactory_Expecter{mock: &_m.Mock} +} + +// Build provides a mock function with given fields: ctx, r +func (_m *MockFactory) Build(ctx context.Context, r *v0alpha1.Repository) (Repository, error) { + ret := _m.Called(ctx, r) + + if len(ret) == 0 { + panic("no return value specified for Build") + } + + var r0 Repository + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Repository) (Repository, error)); ok { + return rf(ctx, r) + } + if rf, ok := ret.Get(0).(func(context.Context, *v0alpha1.Repository) Repository); ok { + r0 = rf(ctx, r) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(Repository) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *v0alpha1.Repository) error); ok { + r1 = rf(ctx, r) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockFactory_Build_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Build' +type MockFactory_Build_Call struct { + *mock.Call +} + +// Build is a helper method to define mock.On call +// - ctx context.Context +// - r *v0alpha1.Repository +func (_e *MockFactory_Expecter) Build(ctx interface{}, r interface{}) *MockFactory_Build_Call { + return &MockFactory_Build_Call{Call: _e.mock.On("Build", ctx, r)} +} + +func (_c *MockFactory_Build_Call) Run(run func(ctx context.Context, r *v0alpha1.Repository)) *MockFactory_Build_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*v0alpha1.Repository)) + }) + return _c +} + +func (_c *MockFactory_Build_Call) Return(_a0 Repository, _a1 error) *MockFactory_Build_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockFactory_Build_Call) RunAndReturn(run func(context.Context, *v0alpha1.Repository) (Repository, error)) *MockFactory_Build_Call { + _c.Call.Return(run) + return _c +} + +// Mutate provides a mock function with given fields: ctx, obj +func (_m *MockFactory) Mutate(ctx context.Context, obj runtime.Object) error { + ret := _m.Called(ctx, obj) + + if len(ret) == 0 { + panic("no return value specified for Mutate") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, runtime.Object) error); ok { + r0 = rf(ctx, obj) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockFactory_Mutate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Mutate' +type MockFactory_Mutate_Call struct { + *mock.Call +} + +// Mutate is a helper method to define mock.On call +// - ctx context.Context +// - obj runtime.Object +func (_e *MockFactory_Expecter) Mutate(ctx interface{}, obj interface{}) *MockFactory_Mutate_Call { + return &MockFactory_Mutate_Call{Call: _e.mock.On("Mutate", ctx, obj)} +} + +func (_c *MockFactory_Mutate_Call) Run(run func(ctx context.Context, obj runtime.Object)) *MockFactory_Mutate_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(runtime.Object)) + }) + return _c +} + +func (_c *MockFactory_Mutate_Call) Return(_a0 error) *MockFactory_Mutate_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockFactory_Mutate_Call) RunAndReturn(run func(context.Context, runtime.Object) error) *MockFactory_Mutate_Call { + _c.Call.Return(run) + return _c +} + +// Types provides a mock function with no fields +func (_m *MockFactory) Types() []v0alpha1.RepositoryType { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Types") + } + + var r0 []v0alpha1.RepositoryType + if rf, ok := ret.Get(0).(func() []v0alpha1.RepositoryType); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]v0alpha1.RepositoryType) + } + } + + return r0 +} + +// MockFactory_Types_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Types' +type MockFactory_Types_Call struct { + *mock.Call +} + +// Types is a helper method to define mock.On call +func (_e *MockFactory_Expecter) Types() *MockFactory_Types_Call { + return &MockFactory_Types_Call{Call: _e.mock.On("Types")} +} + +func (_c *MockFactory_Types_Call) Run(run func()) *MockFactory_Types_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *MockFactory_Types_Call) Return(_a0 []v0alpha1.RepositoryType) *MockFactory_Types_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockFactory_Types_Call) RunAndReturn(run func() []v0alpha1.RepositoryType) *MockFactory_Types_Call { + _c.Call.Return(run) + return _c +} + +// NewMockFactory creates a new instance of MockFactory. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockFactory(t interface { + mock.TestingT + Cleanup(func()) +}) *MockFactory { + mock := &MockFactory{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/apps/provisioning/pkg/repository/finalizers.go b/apps/provisioning/pkg/repository/finalizers.go index ea8ae2a9021..9b674e21242 100644 --- a/apps/provisioning/pkg/repository/finalizers.go +++ b/apps/provisioning/pkg/repository/finalizers.go @@ -8,3 +8,9 @@ const ReleaseOrphanResourcesFinalizer = "release-orphan-resources" // CleanFinalizer calls the "OnDelete" function for resource const CleanFinalizer = "cleanup" + +var SupportedFinalizers = []string{ + RemoveOrphanResourcesFinalizer, + ReleaseOrphanResourcesFinalizer, + CleanFinalizer, +} diff --git a/apps/provisioning/pkg/repository/github/client.go b/apps/provisioning/pkg/repository/github/client.go index 5b21a5a4f1e..00f1ca0946b 100644 --- a/apps/provisioning/pkg/repository/github/client.go +++ b/apps/provisioning/pkg/repository/github/client.go @@ -13,6 +13,7 @@ import ( // API errors that we need to convey after parsing real GH errors (or faking them). var ( ErrResourceNotFound = errors.New("the resource does not exist") + ErrUnauthorized = errors.New("unauthorized") //lint:ignore ST1005 this is not punctuation ErrServiceUnavailable = apierrors.NewServiceUnavailable("github is unavailable") ErrTooManyItems = errors.New("maximum number of items exceeded") diff --git a/apps/provisioning/pkg/repository/github/impl.go b/apps/provisioning/pkg/repository/github/impl.go index c9f5468bc8d..c80f1e7a7f9 100644 --- a/apps/provisioning/pkg/repository/github/impl.go +++ b/apps/provisioning/pkg/repository/github/impl.go @@ -199,6 +199,9 @@ func (r *githubClient) DeleteWebhook(ctx context.Context, owner, repository stri if ghErr.Response.StatusCode == http.StatusNotFound { return ErrResourceNotFound } + if ghErr.Response.StatusCode == http.StatusUnauthorized || ghErr.Response.StatusCode == http.StatusForbidden { + return ErrUnauthorized + } return err } diff --git a/apps/provisioning/pkg/repository/github/impl_test.go b/apps/provisioning/pkg/repository/github/impl_test.go index fedbc2a9850..57f954e906e 100644 --- a/apps/provisioning/pkg/repository/github/impl_test.go +++ b/apps/provisioning/pkg/repository/github/impl_test.go @@ -975,6 +975,27 @@ func TestGithubClient_DeleteWebhook(t *testing.T) { webhookID: 789, wantErr: ErrServiceUnavailable, }, + { + name: "unauthorized to delete the webhook", + mockHandler: mockhub.NewMockedHTTPClient( + mockhub.WithRequestMatchHandler( + mockhub.DeleteReposHooksByOwnerByRepoByHookId, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + require.NoError(t, json.NewEncoder(w).Encode(github.ErrorResponse{ + Response: &http.Response{ + StatusCode: http.StatusUnauthorized, + }, + Message: "401 bad credentials", + })) + }), + ), + ), + owner: "test-owner", + repository: "test-repo", + webhookID: 789, + wantErr: ErrUnauthorized, + }, { name: "other error", mockHandler: mockhub.NewMockedHTTPClient( diff --git a/apps/provisioning/pkg/repository/github/testdata/webhook-push-keep_file_only.json b/apps/provisioning/pkg/repository/github/testdata/webhook-push-keep_file_only.json new file mode 100644 index 00000000000..f4ab95c170e --- /dev/null +++ b/apps/provisioning/pkg/repository/github/testdata/webhook-push-keep_file_only.json @@ -0,0 +1,105 @@ +{ + "ref": "refs/heads/main", + "before": "72096e3adc646c5a5b8a91744f962b12bac06045", + "after": "1234567890abcdef1234567890abcdef12345678", + "repository": { + "id": 888020043, + "node_id": "R_kgDONO4cSw", + "name": "git-ui-sync-demo", + "full_name": "grafana/git-ui-sync-demo", + "private": true, + "owner": { + "name": "grafana", + "email": "hello@grafana.com", + "login": "grafana", + "id": 7195757, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/grafana", + "html_url": "https://github.com/grafana", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/grafana/git-ui-sync-demo", + "description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.", + "fork": false, + "url": "https://github.com/grafana/git-ui-sync-demo", + "default_branch": "main", + "master_branch": "main", + "organization": "grafana" + }, + "pusher": { + "name": "testuser", + "email": "test@grafana.com" + }, + "organization": { + "login": "grafana", + "id": 7195757, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=", + "url": "https://api.github.com/orgs/grafana", + "avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4" + }, + "sender": { + "login": "testuser", + "id": 123456, + "node_id": "MDQ6VXNlcjEyMzQ1Ng==", + "avatar_url": "https://avatars.githubusercontent.com/u/123456?v=4", + "type": "User", + "site_admin": false + }, + "created": false, + "deleted": false, + "forced": false, + "base_ref": null, + "compare": "https://github.com/grafana/git-ui-sync-demo/compare/72096e3adc64...1234567890ab", + "commits": [ + { + "id": "1234567890abcdef1234567890abcdef12345678", + "tree_id": "abcdef1234567890abcdef1234567890abcdef12", + "distinct": true, + "message": "Remove empty folder by deleting .keep file", + "timestamp": "2024-12-09T11:00:48+03:00", + "url": "https://github.com/grafana/git-ui-sync-demo/commit/1234567890abcdef1234567890abcdef12345678", + "author": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "committer": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "added": [], + "removed": [ + "empty-folder/.keep" + ], + "modified": [] + } + ], + "head_commit": { + "id": "1234567890abcdef1234567890abcdef12345678", + "tree_id": "abcdef1234567890abcdef1234567890abcdef12", + "distinct": true, + "message": "Remove empty folder by deleting .keep file", + "timestamp": "2024-12-09T11:00:48+03:00", + "url": "https://github.com/grafana/git-ui-sync-demo/commit/1234567890abcdef1234567890abcdef12345678", + "author": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "committer": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "added": [], + "removed": [ + "empty-folder/.keep" + ], + "modified": [] + } +} + diff --git a/apps/provisioning/pkg/repository/github/testdata/webhook-push-keep_file_with_others.json b/apps/provisioning/pkg/repository/github/testdata/webhook-push-keep_file_with_others.json new file mode 100644 index 00000000000..9a70ca4997c --- /dev/null +++ b/apps/provisioning/pkg/repository/github/testdata/webhook-push-keep_file_with_others.json @@ -0,0 +1,109 @@ +{ + "ref": "refs/heads/main", + "before": "72096e3adc646c5a5b8a91744f962b12bac06045", + "after": "2345678901bcdef2345678901bcdef2345678901", + "repository": { + "id": 888020043, + "node_id": "R_kgDONO4cSw", + "name": "git-ui-sync-demo", + "full_name": "grafana/git-ui-sync-demo", + "private": true, + "owner": { + "name": "grafana", + "email": "hello@grafana.com", + "login": "grafana", + "id": 7195757, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/grafana", + "html_url": "https://github.com/grafana", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/grafana/git-ui-sync-demo", + "description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.", + "fork": false, + "url": "https://github.com/grafana/git-ui-sync-demo", + "default_branch": "main", + "master_branch": "main", + "organization": "grafana" + }, + "pusher": { + "name": "testuser", + "email": "test@grafana.com" + }, + "organization": { + "login": "grafana", + "id": 7195757, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=", + "url": "https://api.github.com/orgs/grafana", + "avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4" + }, + "sender": { + "login": "testuser", + "id": 123456, + "node_id": "MDQ6VXNlcjEyMzQ1Ng==", + "avatar_url": "https://avatars.githubusercontent.com/u/123456?v=4", + "type": "User", + "site_admin": false + }, + "created": false, + "deleted": false, + "forced": false, + "base_ref": null, + "compare": "https://github.com/grafana/git-ui-sync-demo/compare/72096e3adc64...2345678901bc", + "commits": [ + { + "id": "2345678901bcdef2345678901bcdef2345678901", + "tree_id": "bcdef2345678901bcdef2345678901bcdef23456", + "distinct": true, + "message": "Remove folder with .keep and dashboard files", + "timestamp": "2024-12-09T11:00:48+03:00", + "url": "https://github.com/grafana/git-ui-sync-demo/commit/2345678901bcdef2345678901bcdef2345678901", + "author": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "committer": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "added": [], + "removed": [ + "dashboards/.keep", + "dashboards/dashboard1.json", + "dashboards/dashboard2.json" + ], + "modified": [] + } + ], + "head_commit": { + "id": "2345678901bcdef2345678901bcdef2345678901", + "tree_id": "bcdef2345678901bcdef2345678901bcdef23456", + "distinct": true, + "message": "Remove folder with .keep and dashboard files", + "timestamp": "2024-12-09T11:00:48+03:00", + "url": "https://github.com/grafana/git-ui-sync-demo/commit/2345678901bcdef2345678901bcdef2345678901", + "author": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "committer": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "added": [], + "removed": [ + "dashboards/.keep", + "dashboards/dashboard1.json", + "dashboards/dashboard2.json" + ], + "modified": [] + } +} + diff --git a/apps/provisioning/pkg/repository/github/testdata/webhook-push-multiple_keep_files.json b/apps/provisioning/pkg/repository/github/testdata/webhook-push-multiple_keep_files.json new file mode 100644 index 00000000000..cbb82242aa3 --- /dev/null +++ b/apps/provisioning/pkg/repository/github/testdata/webhook-push-multiple_keep_files.json @@ -0,0 +1,109 @@ +{ + "ref": "refs/heads/main", + "before": "72096e3adc646c5a5b8a91744f962b12bac06045", + "after": "3456789012cdef3456789012cdef3456789012cd", + "repository": { + "id": 888020043, + "node_id": "R_kgDONO4cSw", + "name": "git-ui-sync-demo", + "full_name": "grafana/git-ui-sync-demo", + "private": true, + "owner": { + "name": "grafana", + "email": "hello@grafana.com", + "login": "grafana", + "id": 7195757, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=", + "avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/grafana", + "html_url": "https://github.com/grafana", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/grafana/git-ui-sync-demo", + "description": "A repository containing Grafana dashboards to demo the Github Sync feature in Grafana.", + "fork": false, + "url": "https://github.com/grafana/git-ui-sync-demo", + "default_branch": "main", + "master_branch": "main", + "organization": "grafana" + }, + "pusher": { + "name": "testuser", + "email": "test@grafana.com" + }, + "organization": { + "login": "grafana", + "id": 7195757, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjcxOTU3NTc=", + "url": "https://api.github.com/orgs/grafana", + "avatar_url": "https://avatars.githubusercontent.com/u/7195757?v=4" + }, + "sender": { + "login": "testuser", + "id": 123456, + "node_id": "MDQ6VXNlcjEyMzQ1Ng==", + "avatar_url": "https://avatars.githubusercontent.com/u/123456?v=4", + "type": "User", + "site_admin": false + }, + "created": false, + "deleted": false, + "forced": false, + "base_ref": null, + "compare": "https://github.com/grafana/git-ui-sync-demo/compare/72096e3adc64...3456789012cd", + "commits": [ + { + "id": "3456789012cdef3456789012cdef3456789012cd", + "tree_id": "cdef3456789012cdef3456789012cdef34567890", + "distinct": true, + "message": "Remove multiple folders, some with only .keep files", + "timestamp": "2024-12-09T11:00:48+03:00", + "url": "https://github.com/grafana/git-ui-sync-demo/commit/3456789012cdef3456789012cdef3456789012cd", + "author": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "committer": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "added": [], + "removed": [ + "empty-folder1/.keep", + "dashboards-to-delete/.keep", + "dashboards-to-delete/dashboard.json" + ], + "modified": [] + } + ], + "head_commit": { + "id": "3456789012cdef3456789012cdef3456789012cd", + "tree_id": "cdef3456789012cdef3456789012cdef34567890", + "distinct": true, + "message": "Remove multiple folders, some with only .keep files", + "timestamp": "2024-12-09T11:00:48+03:00", + "url": "https://github.com/grafana/git-ui-sync-demo/commit/3456789012cdef3456789012cdef3456789012cd", + "author": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "committer": { + "name": "Test User", + "email": "test@grafana.com", + "username": "testuser" + }, + "added": [], + "removed": [ + "empty-folder1/.keep", + "dashboards-to-delete/.keep", + "dashboards-to-delete/dashboard.json" + ], + "modified": [] + } +} + diff --git a/apps/provisioning/pkg/repository/github/webhook.go b/apps/provisioning/pkg/repository/github/webhook.go index 080030253e0..40f87ddc93d 100644 --- a/apps/provisioning/pkg/repository/github/webhook.go +++ b/apps/provisioning/pkg/repository/github/webhook.go @@ -7,6 +7,7 @@ import ( "log/slog" "net/http" "slices" + "strings" "github.com/google/go-github/v70/github" "github.com/google/uuid" @@ -15,6 +16,7 @@ import ( "github.com/grafana/grafana-app-sdk/logging" provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" "github.com/grafana/grafana/apps/provisioning/pkg/repository" + "github.com/grafana/grafana/apps/provisioning/pkg/safepath" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" ) @@ -119,13 +121,38 @@ func (r *githubWebhookRepository) parsePushEvent(event *github.PushEvent) (*prov return &provisioning.WebhookResponse{Code: http.StatusOK}, nil } + // whenever possible, we want to do incremental syncs to keep things performant. + // however, if we get an event where just a .keep file is being deleted, and no other files in the folder + // are being deleted, the folder could be gone from git, but not from grafana and we do not have a way + // to get the grafana uid to delete the folder. so, instead, we will queue a full sync to clean things up. + dirsWithKeepDeletes := make(map[string]struct{}) + dirsWithOtherDeletes := make(map[string]struct{}) + for _, change := range event.GetCommits() { + for _, removedFile := range change.Removed { + dir := safepath.Dir(removedFile) + if strings.HasSuffix(removedFile, ".keep") { + dirsWithKeepDeletes[dir] = struct{}{} + } else { + dirsWithOtherDeletes[dir] = struct{}{} + } + } + } + // if there are any keep files deleted that do not have other files deleted in the same folder, we need to queue a full sync + incremental := true + for dir := range dirsWithKeepDeletes { + if _, exists := dirsWithOtherDeletes[dir]; !exists { + incremental = false + break + } + } + return &provisioning.WebhookResponse{ Code: http.StatusAccepted, Job: &provisioning.JobSpec{ Repository: r.config.GetName(), Action: provisioning.JobActionPull, Pull: &provisioning.SyncJobOptions{ - Incremental: true, + Incremental: incremental, }, }, }, nil @@ -273,9 +300,18 @@ func (r *githubWebhookRepository) deleteWebhook(ctx context.Context) error { id := r.config.Status.Webhook.ID - if err := r.gh.DeleteWebhook(ctx, r.owner, r.repo, id); err != nil { + err := r.gh.DeleteWebhook(ctx, r.owner, r.repo, id) + if err != nil && !errors.Is(err, ErrResourceNotFound) && !errors.Is(err, ErrUnauthorized) { return fmt.Errorf("delete webhook: %w", err) } + if errors.Is(err, ErrResourceNotFound) { + logger.Warn("webhook no longer exists", "url", r.config.Status.Webhook.URL, "id", id) + return nil + } + if errors.Is(err, ErrUnauthorized) { + logger.Warn("webhook deletion failed. no longer authorized to delete this webhook", "url", r.config.Status.Webhook.URL, "id", id) + return nil + } logger.Info("webhook deleted", "url", r.config.Status.Webhook.URL, "id", id) return nil diff --git a/apps/provisioning/pkg/repository/github/webhook_test.go b/apps/provisioning/pkg/repository/github/webhook_test.go index 24127f7359d..5dc23c70e34 100644 --- a/apps/provisioning/pkg/repository/github/webhook_test.go +++ b/apps/provisioning/pkg/repository/github/webhook_test.go @@ -69,6 +69,36 @@ func TestParseWebhooks(t *testing.T) { }, }, }}, + {"push", "keep_file_only", provisioning.WebhookResponse{ + Code: http.StatusAccepted, + Job: &provisioning.JobSpec{ + Repository: "unit-test-repo", + Action: provisioning.JobActionPull, + Pull: &provisioning.SyncJobOptions{ + Incremental: false, + }, + }, + }}, + {"push", "keep_file_with_others", provisioning.WebhookResponse{ + Code: http.StatusAccepted, + Job: &provisioning.JobSpec{ + Repository: "unit-test-repo", + Action: provisioning.JobActionPull, + Pull: &provisioning.SyncJobOptions{ + Incremental: true, + }, + }, + }}, + {"push", "multiple_keep_files", provisioning.WebhookResponse{ + Code: http.StatusAccepted, + Job: &provisioning.JobSpec{ + Repository: "unit-test-repo", + Action: provisioning.JobActionPull, + Pull: &provisioning.SyncJobOptions{ + Incremental: false, + }, + }, + }}, {"issue_comment", "created", provisioning.WebhookResponse{ Code: http.StatusNotImplemented, }}, @@ -1539,6 +1569,58 @@ func TestGitHubRepository_OnDelete(t *testing.T) { webhookURL: "https://example.com/webhook", expectedError: nil, }, + { + name: "webhook not found during deletion", + setupMock: func(m *MockClient) { + m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)). + Return(ErrResourceNotFound) + }, + config: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + }, + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/webhook", + }, + }, + }, + webhookURL: "https://example.com/webhook", + // We don't return an error if the webhook is already gone + expectedError: nil, + }, + { + name: "unauthorized to delete the webhook", + setupMock: func(m *MockClient) { + m.On("DeleteWebhook", mock.Anything, "grafana", "grafana", int64(123)). + Return(ErrUnauthorized) + }, + config: &provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-repo", + }, + Spec: provisioning.RepositorySpec{ + GitHub: &provisioning.GitHubRepositoryConfig{ + Branch: "main", + }, + }, + Status: provisioning.RepositoryStatus{ + Webhook: &provisioning.WebhookStatus{ + ID: 123, + URL: "https://example.com/webhook", + }, + }, + }, + webhookURL: "https://example.com/webhook", + // We don't return an error if access to the webhook is revoked + expectedError: nil, + }, { name: "no webhook URL provided", setupMock: func(_ *MockClient) {}, diff --git a/apps/provisioning/pkg/repository/tester.go b/apps/provisioning/pkg/repository/tester.go new file mode 100644 index 00000000000..6bd65443d92 --- /dev/null +++ b/apps/provisioning/pkg/repository/tester.go @@ -0,0 +1,84 @@ +package repository + +import ( + "context" + "net/http" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +// SimpleRepositoryTester will validate the repository configuration, and then proceed to test the connection to the repository +type SimpleRepositoryTester struct { + validator RepositoryValidator +} + +func NewSimpleRepositoryTester(validator RepositoryValidator) SimpleRepositoryTester { + return SimpleRepositoryTester{ + validator: validator, + } +} + +// TestRepository validates the repository and then runs a health check +func (t *SimpleRepositoryTester) TestRepository(ctx context.Context, repo Repository) (*provisioning.TestResults, error) { + errors := t.validator.ValidateRepository(repo) + if len(errors) > 0 { + rsp := &provisioning.TestResults{ + Code: http.StatusUnprocessableEntity, // Invalid + Success: false, + Errors: make([]provisioning.ErrorDetails, len(errors)), + } + for i, err := range errors { + rsp.Errors[i] = provisioning.ErrorDetails{ + Type: metav1.CauseType(err.Type), + Field: err.Field, + Detail: err.Detail, + } + } + return rsp, nil + } + + return repo.Test(ctx) +} + +type VerifyAgainstExistingRepositories func(ctx context.Context, cfg *provisioning.Repository) *field.Error // defined this way to prevent an import cycle + +// RepositoryTesterWithExistingChecker will validate the repository configuration, run a health check, and then compare it against existing repositories +type RepositoryTesterWithExistingChecker struct { + tester SimpleRepositoryTester + verify VerifyAgainstExistingRepositories +} + +func NewRepositoryTesterWithExistingChecker(tester SimpleRepositoryTester, verify VerifyAgainstExistingRepositories) RepositoryTesterWithExistingChecker { + return RepositoryTesterWithExistingChecker{ + tester: tester, + verify: verify, + } +} + +// TestRepositoryAndCheckExisting validates the repository, runs a health check, and then compares it against existing repositories +func (c *RepositoryTesterWithExistingChecker) TestRepositoryAndCheckExisting(ctx context.Context, repo Repository) (*provisioning.TestResults, error) { + rsp, err := c.tester.TestRepository(ctx, repo) + if err != nil { + return nil, err + } + + if rsp.Success { + cfg := repo.Config() + if validationErr := c.verify(ctx, cfg); validationErr != nil { + rsp = &provisioning.TestResults{ + Success: false, + Code: http.StatusUnprocessableEntity, + Errors: []provisioning.ErrorDetails{{ + Type: metav1.CauseType(validationErr.Type), + Field: validationErr.Field, + Detail: validationErr.Detail, + }}, + } + } + } + + return rsp, nil +} diff --git a/apps/provisioning/pkg/repository/tester_test.go b/apps/provisioning/pkg/repository/tester_test.go new file mode 100644 index 00000000000..91f29d15ceb --- /dev/null +++ b/apps/provisioning/pkg/repository/tester_test.go @@ -0,0 +1,204 @@ +package repository + +import ( + "context" + "fmt" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + + provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" +) + +func TestTestRepository(t *testing.T) { + tests := []struct { + name string + repository *MockRepository + expectedCode int + expectedErrs []provisioning.ErrorDetails + expectedError error + }{ + { + name: "validation fails", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + // Missing required title + }, + }) + m.On("Validate").Return(field.ErrorList{}) + return m + }(), + expectedCode: http.StatusUnprocessableEntity, + expectedErrs: []provisioning.ErrorDetails{{ + Type: metav1.CauseTypeFieldValueRequired, + Field: "spec.title", + Detail: "a repository title must be given", + }}, + }, + { + name: "test passes", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + }, + }) + m.On("Validate").Return(field.ErrorList{}) + m.On("Test", mock.Anything).Return(&provisioning.TestResults{ + Code: http.StatusOK, + Success: true, + }, nil) + return m + }(), + expectedCode: http.StatusOK, + expectedErrs: nil, + }, + { + name: "test fails with error", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + }, + }) + m.On("Validate").Return(field.ErrorList{}) + m.On("Test", mock.Anything).Return(nil, fmt.Errorf("test error")) + return m + }(), + expectedError: fmt.Errorf("test error"), + }, + { + name: "test fails with results", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + }, + }) + m.On("Validate").Return(field.ErrorList{}) + m.On("Test", mock.Anything).Return(&provisioning.TestResults{ + Code: http.StatusBadRequest, + Success: false, + Errors: []provisioning.ErrorDetails{{ + Type: metav1.CauseTypeFieldValueInvalid, + Field: "spec.property", + }}, + }, nil) + return m + }(), + expectedCode: http.StatusBadRequest, + expectedErrs: []provisioning.ErrorDetails{{ + Type: metav1.CauseTypeFieldValueInvalid, + Field: "spec.property", + }}, + }, + } + + tester := NewSimpleRepositoryTester(NewValidator(10*time.Second, []provisioning.SyncTargetType{provisioning.SyncTargetTypeFolder, provisioning.SyncTargetTypeInstance}, true)) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + results, err := tester.TestRepository(context.Background(), tt.repository) + + if tt.expectedError != nil { + require.Error(t, err) + require.Equal(t, tt.expectedError.Error(), err.Error()) + return + } + + require.NoError(t, err) + require.NotNil(t, results) + require.Equal(t, tt.expectedCode, results.Code) + + if tt.expectedErrs != nil { + require.Equal(t, tt.expectedErrs, results.Errors) + require.False(t, results.Success) + } else { + require.True(t, results.Success) + require.Empty(t, results.Errors) + } + }) + } +} + +func TestTester_TestRepository(t *testing.T) { + repository := NewMockRepository(t) + repository.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + }, + }) + repository.On("Validate").Return(field.ErrorList{}) + repository.On("Test", mock.Anything).Return(&provisioning.TestResults{ + Code: http.StatusOK, + Success: true, + }, nil) + + tester := NewSimpleRepositoryTester(NewValidator(10*time.Second, []provisioning.SyncTargetType{provisioning.SyncTargetTypeFolder, provisioning.SyncTargetTypeInstance}, true)) + results, err := tester.TestRepository(context.Background(), repository) + require.NoError(t, err) + require.NotNil(t, results) + require.Equal(t, http.StatusOK, results.Code) + require.True(t, results.Success) +} + +func TestFromFieldError(t *testing.T) { + tests := []struct { + name string + fieldError *field.Error + expectedCode int + expectedField string + expectedType metav1.CauseType + expectedDetail string + }{ + { + name: "required field error", + fieldError: &field.Error{ + Type: field.ErrorTypeRequired, + Field: "spec.title", + Detail: "a repository title must be given", + }, + expectedCode: http.StatusBadRequest, + expectedField: "spec.title", + expectedType: metav1.CauseTypeFieldValueRequired, + expectedDetail: "a repository title must be given", + }, + { + name: "not supported field error", + fieldError: &field.Error{ + Type: field.ErrorTypeNotSupported, + Field: "spec.workflow", + Detail: "branch is only supported on git repositories", + }, + expectedCode: http.StatusBadRequest, + expectedField: "spec.workflow", + expectedType: metav1.CauseTypeFieldValueNotSupported, + expectedDetail: "branch is only supported on git repositories", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := FromFieldError(tt.fieldError) + + require.NotNil(t, result) + require.Equal(t, tt.expectedCode, result.Code) + require.False(t, result.Success) + require.Len(t, result.Errors, 1) + + errorDetail := result.Errors[0] + require.Equal(t, tt.expectedField, errorDetail.Field) + require.Equal(t, tt.expectedType, errorDetail.Type) + require.Equal(t, tt.expectedDetail, errorDetail.Detail) + }) + } +} diff --git a/apps/provisioning/pkg/repository/test.go b/apps/provisioning/pkg/repository/validator.go similarity index 55% rename from apps/provisioning/pkg/repository/test.go rename to apps/provisioning/pkg/repository/validator.go index b6307237a0a..73198f0d7ee 100644 --- a/apps/provisioning/pkg/repository/test.go +++ b/apps/provisioning/pkg/repository/validator.go @@ -1,10 +1,10 @@ package repository import ( - "context" "fmt" "net/http" "slices" + "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" @@ -12,57 +12,27 @@ import ( provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1" ) -// RepositoryValidator interface for validating repositories against existing ones -type RepositoryValidator interface { - VerifyAgainstExistingRepositories(ctx context.Context, cfg *provisioning.Repository) *field.Error +type RepositoryValidator struct { + allowedTargets []provisioning.SyncTargetType + allowImageRendering bool + minSyncInterval time.Duration } -func TestRepository(ctx context.Context, repo Repository) (*provisioning.TestResults, error) { - return TestRepositoryWithValidator(ctx, repo, nil) +func NewValidator(minSyncInterval time.Duration, allowedTargets []provisioning.SyncTargetType, allowImageRendering bool) RepositoryValidator { + // do not allow minsync interval to be less than 10 + if minSyncInterval <= 10*time.Second { + minSyncInterval = 10 * time.Second + } + + return RepositoryValidator{ + allowedTargets: allowedTargets, + allowImageRendering: allowImageRendering, + minSyncInterval: minSyncInterval, + } } -func TestRepositoryWithValidator(ctx context.Context, repo Repository, validator RepositoryValidator) (*provisioning.TestResults, error) { - errors := ValidateRepository(repo) - if len(errors) > 0 { - rsp := &provisioning.TestResults{ - Code: http.StatusUnprocessableEntity, // Invalid - Success: false, - Errors: make([]provisioning.ErrorDetails, len(errors)), - } - for i, err := range errors { - rsp.Errors[i] = provisioning.ErrorDetails{ - Type: metav1.CauseType(err.Type), - Field: err.Field, - Detail: err.Detail, - } - } - return rsp, nil - } - - rsp, err := repo.Test(ctx) - if err != nil { - return nil, err - } - - if rsp.Success && validator != nil { - cfg := repo.Config() - if validationErr := validator.VerifyAgainstExistingRepositories(ctx, cfg); validationErr != nil { - rsp = &provisioning.TestResults{ - Success: false, - Code: http.StatusUnprocessableEntity, - Errors: []provisioning.ErrorDetails{{ - Type: metav1.CauseType(validationErr.Type), - Field: validationErr.Field, - Detail: validationErr.Detail, - }}, - } - } - } - - return rsp, nil -} - -func ValidateRepository(repo Repository) field.ErrorList { +// ValidateRepository solely does configuration checks on the repository object. It does not run a health check or compare against existing repositories. +func (v *RepositoryValidator) ValidateRepository(repo Repository) field.ErrorList { list := repo.Validate() cfg := repo.Config() @@ -70,14 +40,22 @@ func ValidateRepository(repo Repository) field.ErrorList { list = append(list, field.Required(field.NewPath("spec", "title"), "a repository title must be given")) } - if cfg.Spec.Sync.Enabled && cfg.Spec.Sync.Target == "" { - list = append(list, field.Required(field.NewPath("spec", "sync", "target"), - "The target type is required when sync is enabled")) - } + if cfg.Spec.Sync.Enabled { + if cfg.Spec.Sync.Target == "" { + list = append(list, field.Required(field.NewPath("spec", "sync", "target"), + "The target type is required when sync is enabled")) + } else if !slices.Contains(v.allowedTargets, cfg.Spec.Sync.Target) { + list = append(list, + field.Invalid( + field.NewPath("spec", "target"), + cfg.Spec.Sync.Target, + "sync target is not supported")) + } - if cfg.Spec.Sync.Enabled && cfg.Spec.Sync.IntervalSeconds < 10 { - list = append(list, field.Invalid(field.NewPath("spec", "sync", "intervalSeconds"), - cfg.Spec.Sync.IntervalSeconds, fmt.Sprintf("Interval must be at least %d seconds", 10))) + if cfg.Spec.Sync.IntervalSeconds < int64(v.minSyncInterval.Seconds()) { + list = append(list, field.Invalid(field.NewPath("spec", "sync", "intervalSeconds"), + cfg.Spec.Sync.IntervalSeconds, fmt.Sprintf("Interval must be at least %d seconds", int64(v.minSyncInterval.Seconds())))) + } } // Reserved names (for now) @@ -124,6 +102,25 @@ func ValidateRepository(repo Repository) field.ErrorList { ) } + for _, f := range cfg.Finalizers { + if !slices.Contains(SupportedFinalizers, f) { + list = append(list, + field.Invalid( + field.NewPath("medatada", "finalizers"), + cfg.Finalizers, + fmt.Sprintf("unknown finalizer: %s", f), + ), + ) + } + } + + if !v.allowImageRendering && cfg.Spec.GitHub != nil && cfg.Spec.GitHub.GenerateDashboardPreviews { + list = append(list, + field.Invalid(field.NewPath("spec", "generateDashboardPreviews"), + cfg.Spec.GitHub.GenerateDashboardPreviews, + "image rendering is not enabled")) + } + return list } diff --git a/apps/provisioning/pkg/repository/test_test.go b/apps/provisioning/pkg/repository/validator_test.go similarity index 61% rename from apps/provisioning/pkg/repository/test_test.go rename to apps/provisioning/pkg/repository/validator_test.go index 0a35c024ca4..cb8b726a9bf 100644 --- a/apps/provisioning/pkg/repository/test_test.go +++ b/apps/provisioning/pkg/repository/validator_test.go @@ -1,12 +1,9 @@ package repository import ( - "context" - "fmt" - "net/http" "testing" + "time" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/validation/field" @@ -83,7 +80,7 @@ func TestValidateRepository(t *testing.T) { Title: "Test Repo", Sync: provisioning.SyncOptions{ Enabled: true, - Target: "test", + Target: provisioning.SyncTargetTypeFolder, IntervalSeconds: 5, }, }, @@ -154,6 +151,27 @@ func TestValidateRepository(t *testing.T) { require.Contains(t, errors.ToAggregate().Error(), "spec.github: Invalid value") }, }, + { + name: "github enabled when image rendering is not allowed", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + Type: provisioning.GitHubRepositoryType, + GitHub: &provisioning.GitHubRepositoryConfig{ + GenerateDashboardPreviews: true, + }, + }, + }) + m.On("Validate").Return(field.ErrorList{}) + return m + }(), + expectedErrs: 1, + validateError: func(t *testing.T, errors field.ErrorList) { + require.Contains(t, errors.ToAggregate().Error(), "spec.generateDashboardPreviews: Invalid value") + }, + }, { name: "mismatched git config", repository: func() *MockRepository { @@ -185,17 +203,18 @@ func TestValidateRepository(t *testing.T) { Sync: provisioning.SyncOptions{ Enabled: true, IntervalSeconds: 5, + Target: provisioning.SyncTargetTypeInstance, }, }, }) m.On("Validate").Return(field.ErrorList{}) return m }(), - expectedErrs: 4, // Updated from 3 to 4 to match actual errors: + expectedErrs: 4, // 1. missing title // 2. sync target missing - // 3. sync interval too low - // 4. reserved name + // 3. reserved name + // 4. sync target not supported }, { name: "branch workflow for non-github repository", @@ -257,11 +276,34 @@ func TestValidateRepository(t *testing.T) { require.Contains(t, errors.ToAggregate().Error(), "cannot have both remove and release orphan resources finalizers") }, }, + { + name: "invalid finalizer in the list", + repository: func() *MockRepository { + m := NewMockRepository(t) + m.On("Config").Return(&provisioning.Repository{ + ObjectMeta: metav1.ObjectMeta{ + Finalizers: []string{CleanFinalizer, "invalid-finalizer", RemoveOrphanResourcesFinalizer}, + }, + Spec: provisioning.RepositorySpec{ + Title: "Test Repo", + Type: provisioning.GitHubRepositoryType, + Workflows: []provisioning.Workflow{provisioning.WriteWorkflow}, + }, + }) + m.On("Validate").Return(field.ErrorList{}) + return m + }(), + expectedErrs: 1, + validateError: func(t *testing.T, errors field.ErrorList) { + require.Contains(t, errors.ToAggregate().Error(), "unknown finalizer: invalid-finalizer") + }, + }, } + validator := NewValidator(10*time.Second, []provisioning.SyncTargetType{provisioning.SyncTargetTypeFolder}, false) for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - errors := ValidateRepository(tt.repository) + errors := validator.ValidateRepository(tt.repository) require.Len(t, errors, tt.expectedErrs) if tt.validateError != nil { tt.validateError(t, errors) @@ -269,201 +311,3 @@ func TestValidateRepository(t *testing.T) { }) } } - -func TestTestRepository(t *testing.T) { - tests := []struct { - name string - repository *MockRepository - expectedCode int - expectedErrs []provisioning.ErrorDetails - expectedError error - }{ - { - name: "validation fails", - repository: func() *MockRepository { - m := NewMockRepository(t) - m.On("Config").Return(&provisioning.Repository{ - Spec: provisioning.RepositorySpec{ - // Missing required title - }, - }) - m.On("Validate").Return(field.ErrorList{}) - return m - }(), - expectedCode: http.StatusUnprocessableEntity, - expectedErrs: []provisioning.ErrorDetails{{ - Type: metav1.CauseTypeFieldValueRequired, - Field: "spec.title", - Detail: "a repository title must be given", - }}, - }, - { - name: "test passes", - repository: func() *MockRepository { - m := NewMockRepository(t) - m.On("Config").Return(&provisioning.Repository{ - Spec: provisioning.RepositorySpec{ - Title: "Test Repo", - }, - }) - m.On("Validate").Return(field.ErrorList{}) - m.On("Test", mock.Anything).Return(&provisioning.TestResults{ - Code: http.StatusOK, - Success: true, - }, nil) - return m - }(), - expectedCode: http.StatusOK, - expectedErrs: nil, - }, - { - name: "test fails with error", - repository: func() *MockRepository { - m := NewMockRepository(t) - m.On("Config").Return(&provisioning.Repository{ - Spec: provisioning.RepositorySpec{ - Title: "Test Repo", - }, - }) - m.On("Validate").Return(field.ErrorList{}) - m.On("Test", mock.Anything).Return(nil, fmt.Errorf("test error")) - return m - }(), - expectedError: fmt.Errorf("test error"), - }, - { - name: "test fails with results", - repository: func() *MockRepository { - m := NewMockRepository(t) - m.On("Config").Return(&provisioning.Repository{ - Spec: provisioning.RepositorySpec{ - Title: "Test Repo", - }, - }) - m.On("Validate").Return(field.ErrorList{}) - m.On("Test", mock.Anything).Return(&provisioning.TestResults{ - Code: http.StatusBadRequest, - Success: false, - Errors: []provisioning.ErrorDetails{{ - Type: metav1.CauseTypeFieldValueInvalid, - Field: "spec.property", - }}, - }, nil) - return m - }(), - expectedCode: http.StatusBadRequest, - expectedErrs: []provisioning.ErrorDetails{{ - Type: metav1.CauseTypeFieldValueInvalid, - Field: "spec.property", - }}, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - results, err := TestRepository(context.Background(), tt.repository) - - if tt.expectedError != nil { - require.Error(t, err) - require.Equal(t, tt.expectedError.Error(), err.Error()) - return - } - - require.NoError(t, err) - require.NotNil(t, results) - require.Equal(t, tt.expectedCode, results.Code) - - if tt.expectedErrs != nil { - require.Equal(t, tt.expectedErrs, results.Errors) - require.False(t, results.Success) - } else { - require.True(t, results.Success) - require.Empty(t, results.Errors) - } - }) - } -} - -func TestTester_TestRepository(t *testing.T) { - repository := NewMockRepository(t) - repository.On("Config").Return(&provisioning.Repository{ - Spec: provisioning.RepositorySpec{ - Title: "Test Repo", - }, - }) - repository.On("Validate").Return(field.ErrorList{}) - repository.On("Test", mock.Anything).Return(&provisioning.TestResults{ - Code: http.StatusOK, - Success: true, - }, nil) - - results, err := TestRepository(context.Background(), repository) - require.NoError(t, err) - require.NotNil(t, results) - require.Equal(t, http.StatusOK, results.Code) - require.True(t, results.Success) -} - -func TestFromFieldError(t *testing.T) { - tests := []struct { - name string - fieldError *field.Error - expectedCode int - expectedField string - expectedType metav1.CauseType - expectedDetail string - }{ - { - name: "required field error", - fieldError: &field.Error{ - Type: field.ErrorTypeRequired, - Field: "spec.title", - Detail: "a repository title must be given", - }, - expectedCode: http.StatusBadRequest, - expectedField: "spec.title", - expectedType: metav1.CauseTypeFieldValueRequired, - expectedDetail: "a repository title must be given", - }, - { - name: "invalid field error", - fieldError: &field.Error{ - Type: field.ErrorTypeInvalid, - Field: "spec.sync.intervalSeconds", - Detail: "Interval must be at least 10 seconds", - }, - expectedCode: http.StatusBadRequest, - expectedField: "spec.sync.intervalSeconds", - expectedType: metav1.CauseTypeFieldValueInvalid, - expectedDetail: "Interval must be at least 10 seconds", - }, - { - name: "not supported field error", - fieldError: &field.Error{ - Type: field.ErrorTypeNotSupported, - Field: "spec.workflow", - Detail: "branch is only supported on git repositories", - }, - expectedCode: http.StatusBadRequest, - expectedField: "spec.workflow", - expectedType: metav1.CauseTypeFieldValueNotSupported, - expectedDetail: "branch is only supported on git repositories", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - result := FromFieldError(tt.fieldError) - - require.NotNil(t, result) - require.Equal(t, tt.expectedCode, result.Code) - require.False(t, result.Success) - require.Len(t, result.Errors, 1) - - errorDetail := result.Errors[0] - require.Equal(t, tt.expectedField, errorDetail.Field) - require.Equal(t, tt.expectedType, errorDetail.Type) - require.Equal(t, tt.expectedDetail, errorDetail.Detail) - }) - } -} diff --git a/apps/sdk.mk b/apps/sdk.mk index 529fe145648..2b1f6c8b34d 100644 --- a/apps/sdk.mk +++ b/apps/sdk.mk @@ -1,4 +1,4 @@ -APP_SDK_VERSION = v0.45.0 +APP_SDK_VERSION = v0.46.0 APP_SDK_DIR = $(shell go env GOPATH)/bin/app-sdk-$(APP_SDK_VERSION) APP_SDK_BIN = $(APP_SDK_DIR)/grafana-app-sdk diff --git a/apps/secret/go.mod b/apps/secret/go.mod index c6d3459746f..1a6b9ecc824 100644 --- a/apps/secret/go.mod +++ b/apps/secret/go.mod @@ -3,11 +3,11 @@ module github.com/grafana/grafana/apps/secret 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-20250710134100-1f3dc0533caf github.com/stretchr/testify v1.11.1 google.golang.org/grpc v1.75.1 - google.golang.org/protobuf v1.36.8 + google.golang.org/protobuf v1.36.9 gopkg.in/yaml.v3 v3.0.1 k8s.io/apimachinery v0.34.1 k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b @@ -18,7 +18,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 diff --git a/apps/secret/go.sum b/apps/secret/go.sum index 45f9c909509..712a7a12807 100644 --- a/apps/secret/go.sum +++ b/apps/secret/go.sum @@ -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= @@ -35,8 +35,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-20250710134100-1f3dc0533caf h1:BBGDHffvVNLoYQlXEpbXcxE0vbpq7pm/8OWF5I+UDZg= @@ -170,8 +170,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= diff --git a/apps/secret/pkg/apis/secret/v1beta1/keeper_client_gen.go b/apps/secret/pkg/apis/secret/v1beta1/keeper_client_gen.go new file mode 100644 index 00000000000..c54e3d6f682 --- /dev/null +++ b/apps/secret/pkg/apis/secret/v1beta1/keeper_client_gen.go @@ -0,0 +1,99 @@ +package v1beta1 + +import ( + "context" + + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type KeeperClient struct { + client *resource.TypedClient[*Keeper, *KeeperList] +} + +func NewKeeperClient(client resource.Client) *KeeperClient { + return &KeeperClient{ + client: resource.NewTypedClient[*Keeper, *KeeperList](client, KeeperKind()), + } +} + +func NewKeeperClientFromGenerator(generator resource.ClientGenerator) (*KeeperClient, error) { + c, err := generator.ClientFor(KeeperKind()) + if err != nil { + return nil, err + } + return NewKeeperClient(c), nil +} + +func (c *KeeperClient) Get(ctx context.Context, identifier resource.Identifier) (*Keeper, error) { + return c.client.Get(ctx, identifier) +} + +func (c *KeeperClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*KeeperList, error) { + return c.client.List(ctx, namespace, opts) +} + +func (c *KeeperClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*KeeperList, error) { + resp, err := c.client.List(ctx, namespace, resource.ListOptions{ + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + for resp.GetContinue() != "" { + page, err := c.client.List(ctx, namespace, resource.ListOptions{ + Continue: resp.GetContinue(), + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + resp.SetContinue(page.GetContinue()) + resp.SetResourceVersion(page.GetResourceVersion()) + resp.SetItems(append(resp.GetItems(), page.GetItems()...)) + } + return resp, nil +} + +func (c *KeeperClient) Create(ctx context.Context, obj *Keeper, opts resource.CreateOptions) (*Keeper, error) { + // Make sure apiVersion and kind are set + obj.APIVersion = GroupVersion.Identifier() + obj.Kind = KeeperKind().Kind() + return c.client.Create(ctx, obj, opts) +} + +func (c *KeeperClient) Update(ctx context.Context, obj *Keeper, opts resource.UpdateOptions) (*Keeper, error) { + return c.client.Update(ctx, obj, opts) +} + +func (c *KeeperClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Keeper, error) { + return c.client.Patch(ctx, identifier, req, opts) +} + +func (c *KeeperClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus KeeperStatus, opts resource.UpdateOptions) (*Keeper, error) { + return c.client.Update(ctx, &Keeper{ + TypeMeta: metav1.TypeMeta{ + Kind: KeeperKind().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 *KeeperClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { + return c.client.Delete(ctx, identifier, opts) +} diff --git a/apps/secret/pkg/apis/secret/v1beta1/securevalue_client_gen.go b/apps/secret/pkg/apis/secret/v1beta1/securevalue_client_gen.go new file mode 100644 index 00000000000..241d4cac3c2 --- /dev/null +++ b/apps/secret/pkg/apis/secret/v1beta1/securevalue_client_gen.go @@ -0,0 +1,99 @@ +package v1beta1 + +import ( + "context" + + "github.com/grafana/grafana-app-sdk/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type SecureValueClient struct { + client *resource.TypedClient[*SecureValue, *SecureValueList] +} + +func NewSecureValueClient(client resource.Client) *SecureValueClient { + return &SecureValueClient{ + client: resource.NewTypedClient[*SecureValue, *SecureValueList](client, SecureValueKind()), + } +} + +func NewSecureValueClientFromGenerator(generator resource.ClientGenerator) (*SecureValueClient, error) { + c, err := generator.ClientFor(SecureValueKind()) + if err != nil { + return nil, err + } + return NewSecureValueClient(c), nil +} + +func (c *SecureValueClient) Get(ctx context.Context, identifier resource.Identifier) (*SecureValue, error) { + return c.client.Get(ctx, identifier) +} + +func (c *SecureValueClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*SecureValueList, error) { + return c.client.List(ctx, namespace, opts) +} + +func (c *SecureValueClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*SecureValueList, error) { + resp, err := c.client.List(ctx, namespace, resource.ListOptions{ + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + for resp.GetContinue() != "" { + page, err := c.client.List(ctx, namespace, resource.ListOptions{ + Continue: resp.GetContinue(), + ResourceVersion: opts.ResourceVersion, + Limit: opts.Limit, + LabelFilters: opts.LabelFilters, + FieldSelectors: opts.FieldSelectors, + }) + if err != nil { + return nil, err + } + resp.SetContinue(page.GetContinue()) + resp.SetResourceVersion(page.GetResourceVersion()) + resp.SetItems(append(resp.GetItems(), page.GetItems()...)) + } + return resp, nil +} + +func (c *SecureValueClient) Create(ctx context.Context, obj *SecureValue, opts resource.CreateOptions) (*SecureValue, error) { + // Make sure apiVersion and kind are set + obj.APIVersion = GroupVersion.Identifier() + obj.Kind = SecureValueKind().Kind() + return c.client.Create(ctx, obj, opts) +} + +func (c *SecureValueClient) Update(ctx context.Context, obj *SecureValue, opts resource.UpdateOptions) (*SecureValue, error) { + return c.client.Update(ctx, obj, opts) +} + +func (c *SecureValueClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*SecureValue, error) { + return c.client.Patch(ctx, identifier, req, opts) +} + +func (c *SecureValueClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus SecureValueStatus, opts resource.UpdateOptions) (*SecureValue, error) { + return c.client.Update(ctx, &SecureValue{ + TypeMeta: metav1.TypeMeta{ + Kind: SecureValueKind().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 *SecureValueClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error { + return c.client.Delete(ctx, identifier, opts) +} diff --git a/apps/secret/pkg/apis/secret_manifest.go b/apps/secret/pkg/apis/secret_manifest.go index 512e381f77d..e83afe68aee 100644 --- a/apps/secret/pkg/apis/secret_manifest.go +++ b/apps/secret/pkg/apis/secret_manifest.go @@ -11,13 +11,16 @@ 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" v1beta1 "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1" ) var appManifestData = app.ManifestData{ - AppName: "secret", - Group: "secret.grafana.app", + AppName: "secret", + Group: "secret.grafana.app", + PreferredVersion: "v1beta1", Versions: []app.ManifestVersion{ { Name: "v1beta1", @@ -37,6 +40,10 @@ var appManifestData = app.ManifestData{ Conversion: false, }, }, + Routes: app.ManifestVersionRoutes{ + Namespaced: map[string]spec3.PathProps{}, + Cluster: map[string]spec3.PathProps{}, + }, }, }, } @@ -66,6 +73,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 "/" 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:] @@ -73,3 +81,42 @@ func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (g goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] return goType, exists } + +var customRouteToGoParamsType = map[string]runtime.Object{} + +func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goType runtime.Object, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoParamsType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + +var customRouteToGoRequestBodyType = map[string]any{} + +func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + +type GoTypeAssociator struct{} + +func NewGoTypeAssociator() *GoTypeAssociator { + return &GoTypeAssociator{} +} + +func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) { + return ManifestGoTypeAssociator(kind, version) +} +func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb string) (goType any, exists bool) { + return ManifestCustomRouteResponsesAssociator(kind, version, path, verb) +} +func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) { + return ManifestCustomRouteQueryAssociator(kind, version, path, verb) +} +func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) { + return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb) +} diff --git a/apps/shorturl/go.mod b/apps/shorturl/go.mod index e812815f1de..7eb91409ca0 100644 --- a/apps/shorturl/go.mod +++ b/apps/shorturl/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/apps/shorturl 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 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250915132226-585b53bc7dba k8s.io/apimachinery v0.34.1 @@ -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 @@ -33,8 +33,8 @@ 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/types v0.0.0-20250917093142-83a502239781 // 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/dskit v0.0.0-20250908063411-6b6da59b5cc4 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect @@ -84,7 +84,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 diff --git a/apps/shorturl/go.sum b/apps/shorturl/go.sum index af2f834077e..06c31439b6e 100644 --- a/apps/shorturl/go.sum +++ b/apps/shorturl/go.sum @@ -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= @@ -48,14 +48,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-20250915132226-585b53bc7dba h1:Qam8QzVRsyZN39zgZ9Vj6e8PEfswvv2McnqCZ/v5NcI= @@ -217,8 +217,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= diff --git a/apps/shorturl/kinds/shorturl.cue b/apps/shorturl/kinds/shorturl.cue index 13938b56b07..ea234173776 100644 --- a/apps/shorturl/kinds/shorturl.cue +++ b/apps/shorturl/kinds/shorturl.cue @@ -20,6 +20,7 @@ shorturl: { response: { url: string } + responseMetadata: typeMeta: false } } } diff --git a/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_getgoto_types_gen.go b/apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_getgoto_response_types_gen.go similarity index 100% rename from apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_getgoto_types_gen.go rename to apps/shorturl/pkg/apis/shorturl/v1alpha1/shorturl_getgoto_response_types_gen.go diff --git a/apps/shorturl/pkg/apis/shorturl_manifest.go b/apps/shorturl/pkg/apis/shorturl_manifest.go index 5d5b718a681..c4446d924e9 100644 --- a/apps/shorturl/pkg/apis/shorturl_manifest.go +++ b/apps/shorturl/pkg/apis/shorturl_manifest.go @@ -26,8 +26,9 @@ var ( ) var appManifestData = app.ManifestData{ - AppName: "shorturl", - Group: "shorturl.grafana.app", + AppName: "shorturl", + Group: "shorturl.grafana.app", + PreferredVersion: "v1alpha1", Versions: []app.ManifestVersion{ { Name: "v1alpha1", @@ -52,7 +53,7 @@ var appManifestData = app.ManifestData{ Get: &spec3.Operation{ OperationProps: spec3.OperationProps{ - OperationId: "GetGoto", + OperationId: "getGoto", Responses: &spec3.Responses{ ResponsesProps: spec3.ResponsesProps{ diff --git a/apps/shorturl/pkg/app/app.go b/apps/shorturl/pkg/app/app.go index ab9b3477ed2..4c85ef97435 100644 --- a/apps/shorturl/pkg/app/app.go +++ b/apps/shorturl/pkg/app/app.go @@ -15,6 +15,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" shorturlv1alpha1 "github.com/grafana/grafana/apps/shorturl/pkg/apis/shorturl/v1alpha1" @@ -39,8 +40,10 @@ func New(cfg app.Config) (app.App, error) { Name: "shorturl", KubeConfig: cfg.KubeConfig, InformerConfig: simple.AppInformerConfig{ - ErrorHandler: func(ctx context.Context, err error) { - klog.ErrorS(err, "Informer processing error") + InformerOptions: operator.InformerOptions{ + ErrorHandler: func(ctx context.Context, err error) { + klog.ErrorS(err, "Informer processing error") + }, }, }, ManagedKinds: []simple.AppManagedKind{ diff --git a/conf/defaults.ini b/conf/defaults.ini index 35eba4152db..1c47f30e6f3 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -1453,6 +1453,10 @@ state_periodic_save_interval = 5m # If the feature flag 'alertingSaveStatePeriodic' is enabled, this is the size of the batch that is saved to the database at once. state_periodic_save_batch_size = 1 +# Enable jitter for periodic state saving to distribute database load over time. +# When enabled, batches are spread across the save interval to prevent load spikes. +state_periodic_save_jitter_enabled = false + # Disables the smoothing of alert evaluations across their evaluation window. # Rules will evaluate in sync. disable_jitter = false @@ -2144,24 +2148,6 @@ show_ui = true # Disables TLS in the secure socks proxy allow_insecure = false -################################## Feature Management ############################################## -# Options to configure the experimental Feature Toggle Admin Page feature, which is behind the `featureToggleAdminPage` feature toggle. Use at your own risk. -[feature_management] -# Allows editing of feature toggles in the feature management page -allow_editing = false - -# Allow customization of URL for the controller that manages feature toggles -update_webhook = - -# Allow configuring an auth token for feature management update requests -update_webhook_token = - -# Hides specific feature toggles from the feature management page -hidden_toggles = - -# Disables updating specific feature toggles in the feature management page -read_only_toggles = - #################################### Public Dashboards ##################################### [public_dashboards] # Set to false to disable public dashboards @@ -2242,4 +2228,9 @@ allowed_targets = instance|folder # Whether image rendering is allowed for dashboard previews. # Requires image rendering service to be configured. -allow_image_rendering = true \ No newline at end of file +allow_image_rendering = true + +# The minimum sync interval that can be set for a repository. This is how often the controller +# will check if there has been any changes to the repository not propagated by a webhook. +# The minimum value is 10 seconds. +min_sync_interval = 10s diff --git a/conf/sample.ini b/conf/sample.ini index 7debeeefd6a..964b3df5196 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -1426,6 +1426,14 @@ # If the feature flag 'alertingSaveStatePeriodic' is enabled, this is the size of the batch that is saved to the database at once. ;state_periodic_save_batch_size = 1 +# Enable jitter for periodic state saves to distribute database load over time. +# When enabled, batches of alert instances are saved with calculated delays between them, +# preventing all instances from being written to the database simultaneously. +# This helps reduce database load spikes during periodic saves, especially beneficial +# in environments with many alert instances or high database contention. +# The jitter delays are distributed within 85% of the save interval to ensure completion before the next cycle. +;state_periodic_save_jitter_enabled = false + # Disables the smoothing of alert evaluations across their evaluation window. # Rules will evaluate in sync. ;disable_jitter = false @@ -2042,20 +2050,6 @@ default_datasource_uid = ; show_ui = true ; allow_insecure = false -################################## Feature Management ############################################## -[feature_management] -# Options to configure the experimental Feature Toggle Admin Page feature, which is behind the `featureToggleAdminPage` feature toggle. Use at your own risk. -# Allow editing of feature toggles in the feature management page -;allow_editing = false -# Allow customization of URL for the controller that manages feature toggles -;update_webhook = -# Allow configuring an auth token for feature management update requests -;update_webhook_token = -# Hide specific feature toggles from the feature management page -;hidden_toggles = -# Disable updating specific feature toggles in the feature management page -;read_only_toggles = - #################################### Public Dashboards ##################################### [public_dashboards] # Set to false to disable public dashboards diff --git a/docs/sources/alerting/alerting-rules/templates/_index.md b/docs/sources/alerting/alerting-rules/templates/_index.md index a2afde85de7..f66781f01e5 100644 --- a/docs/sources/alerting/alerting-rules/templates/_index.md +++ b/docs/sources/alerting/alerting-rules/templates/_index.md @@ -220,6 +220,24 @@ To preview label values, select `Use notification policy`, and then click on `Pr {{< figure src="/media/docs/alerting/alert-instance-routing-preview.png" max-width="1200px" alt="Routing preview displays label values" >}} +## Grafana Cloud AI-generated templates + +Grafana Cloud users can use built-in AI tool to generate templates in the appropriate [alerting template language](/docs/grafana-cloud/alerting-and-irm/alerting/configure-notifications/template-notifications/language/) for you. + +To use AI to create your template, follow these steps: + +1. Go to **Alerting -> Contact points**. + +1. Click the Notification Templates tab then, click the **+ Add notification template group** button. + +1. Name your template. + +1. In the Template group section, click the **Generate with AI** button. + +1. Supply the AI tool with a prompt or select from one of the example prompts and edit that if necessary. + +1. Click **Save**. + ## More information For further details on how to template alert rules, refer to: diff --git a/docs/sources/alerting/fundamentals/alert-rule-evaluation/nodata-and-error-states.md b/docs/sources/alerting/fundamentals/alert-rule-evaluation/nodata-and-error-states.md index f91f9d9f000..50182bd520a 100644 --- a/docs/sources/alerting/fundamentals/alert-rule-evaluation/nodata-and-error-states.md +++ b/docs/sources/alerting/fundamentals/alert-rule-evaluation/nodata-and-error-states.md @@ -106,7 +106,7 @@ A Grafana-managed alert instance can be in any of the following states, dependin The **Error** state is triggered when the alert rule fails to evaluate its query or queries successfully. -This can occur due to evaluation timeouts (default: `30s`) or three repeated failures when querying the data source. The [`evaluation_timeout`](ref:evaluation_timeout) and [`max_attempts`](ref:max_attempts) options control these settings. +This can occur due to evaluation timeouts (default: `30s`) or repeated failures (default: `3`) when querying the data source. The [`evaluation_timeout`](ref:evaluation_timeout) and [`max_attempts`](ref:max_attempts) options control these settings. When an alert instance enters the **Error** state, Grafana, by default, triggers a new [`DatasourceError` alert](#no-data-and-error-alerts). You can control this behavior based on the desired outcome of your alert rule in [Modify the `No Data` or `Error` state](#modify-the-no-data-or-error-state). @@ -157,10 +157,10 @@ To minimize the number of **No Data** or **Error** state alerts received, try th To minimize timeouts resulting in the **Error** state, reduce the time range to request less data every evaluation cycle. -1. Change the default [evaluation time out](ref:evaluation_timeout). The default is set at 30 seconds. To increase the default evaluation timeout, open a support ticket from the [Cloud Portal](https://grafana.com/docs/grafana-cloud/account-management/support/#grafana-cloud-support-options). Note that this should be a last resort, because it may affect the performance of all alert rules and cause missed evaluations if the timeout is too long. - 1. To reduce multiple notifications from **Error** alerts, define a [notification policy](ref:notification-policies) to handle all related alerts with `alertname=DatasourceError`, and filter and group errors from the same data source using the `datasource_uid` label. +1. Change the [evaluation timeout](ref:evaluation_timeout) (default: `30s`) or the [retry mechanism (`max_attempts`)](ref:max_attempts) settings. This should be a last resort, as it can affect the performance of all alert rules and may cause missed evaluations if the timeout is too long. For Grafana Cloud, open a support ticket from the [Cloud Portal](https://grafana.com/docs/grafana-cloud/account-management/support/#grafana-cloud-support-options). + {{< admonition type="tip" >}} For common examples and practical guidance on handling **Error**, **No Data**, and **stale** alert scenarios, refer to the [Handle connectivity errors](ref:guide-connectivity-errors) and [Handle missing data](ref:guide-missing-data) guides. {{< /admonition >}} diff --git a/docs/sources/alerting/set-up/performance-limitations/index.md b/docs/sources/alerting/set-up/performance-limitations/index.md index 72a6515e6cc..82e8ccd41f1 100644 --- a/docs/sources/alerting/set-up/performance-limitations/index.md +++ b/docs/sources/alerting/set-up/performance-limitations/index.md @@ -58,25 +58,54 @@ For more information, refer to [this GitHub issue](https://github.com/grafana/gr ## High load on database caused by a high number of alert instances -If you have a high number of alert instances, it can happen that the load on the database gets very high, as each state -transition of an alert instance is saved in the database after every evaluation. +If you have a high number of alert rules or alert instances, the load on the database can get very high. -### Compressed alert state +By default, Grafana performs one SQL update per alert rule after each evaluation, which updates all alert instances belonging to the rule. -When the `alertingSaveStateCompressed` feature toggle is enabled, Grafana saves the alert rule state in a compressed form. Instead of performing an individual SQL update for each alert instance, Grafana performs a single SQL update per alert rule, updating all alert instances belonging to that rule. - -This can significantly reduce database overhead for alert rules with many alert instances. +You can change this behavior by disabling the `alertingSaveStateCompressed` feature flag. In this case, Grafana performs a separate SQL update for each state change of an alert instance. This configuration is rarely recommended, as it can add significant database overhead for alert rules with many instances. ### Save state periodically -High load can be also prevented by writing to the database periodically, instead of after every evaluation. +You can also reduce database load by writing states periodically instead of after every evaluation. -To save state periodically, enable the `alertingSaveStatePeriodic` feature toggle. +To save state periodically: + +1. Enable the `alertingSaveStatePeriodic` feature toggle. +1. Disable the `alertingSaveStateCompressed` feature toggle. By default, it saves the states every 5 minutes to the database and on each shutdown. The periodic interval can also be configured using the `state_periodic_save_interval` configuration flag. During this process, Grafana deletes all existing alert instances from the database and then writes the entire current set of instances back in batches in a single transaction. Configure the size of each batch using the `state_periodic_save_batch_size` configuration option. +#### Jitter for periodic saves + +To further distribute database load, you can enable jitter for periodic state saves by setting `state_periodic_save_jitter_enabled = true`. When jitter is enabled, instead of saving all batches simultaneously, Grafana spreads the batch writes across a calculated time window of 85% of the save interval. + +**How jitter works:** + +- Calculates delays for each batch: `delay = (batchIndex * timeWindow) / (totalBatches - 1)` +- Time window uses 85% of save interval for safety margin +- Batches are evenly distributed across the time window +- All operations occur within a single database transaction + +**Configuration example:** + +```ini +[unified_alerting] +state_periodic_save_jitter_enabled = true +state_periodic_save_interval = 1m +state_periodic_save_batch_size = 100 +``` + +**Performance impact:** +For 2000 alert instances with 1-minute interval and 100 batch size: + +- Creates 20 batches (2000 ÷ 100) +- Spreads writes across 51 seconds (85% of 60s) +- Batch writes occur every ~2.68 seconds + +This helps reduce database load spikes in environments with high alert cardinality by distributing writes over time rather than concentrating them at the beginning of each save cycle. + The time it takes to write to the database periodically can be monitored using the `state_full_sync_duration_seconds` metric that is exposed by Grafana. diff --git a/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md b/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md index ea79a3686c6..dbe77c05013 100644 --- a/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md +++ b/docs/sources/dashboards/build-dashboards/modify-dashboard-settings/index.md @@ -10,7 +10,6 @@ labels: - cloud - enterprise - oss -menuTitle: Modify dashboard settings title: Modify dashboard settings description: Manage and edit your dashboard settings weight: 8 @@ -56,7 +55,7 @@ To access the dashboard setting page: Adjust dashboard time settings when you want to change the dashboard timezone, the local browser time, and specify auto-refresh time intervals. -1. On the **Settings** page, scroll down to the **Time Options** section of the **General** tab. +1. On the the **General** tab of the **Settings** page, scroll down to the **Time options** section. 1. Specify time settings as follows. - **Time zone:** Specify the local time zone of the service or system that you are monitoring. This can be helpful when monitoring a system or service that operates across several time zones. - **Default:** Grafana uses the default selected time zone for the user profile, team, or organization. If no time zone is specified for the user profile, a team the user is a member of, or the organization, then Grafana uses the local browser time. @@ -71,6 +70,21 @@ Adjust dashboard time settings when you want to change the dashboard timezone, t 1. Click **Save**. 1. Click **Exit edit**. +## Modify graph tooltip behavior + +Use this option to control tooltip and hover highlight behavior across graph panels (for example, time series). + +1. On the the **General** tab of the **Settings** page, scroll down to the **Panel options** section. +1. Choose from the following options to control the tooltip and hover highlight behavior across graph panels: + - **Default** - Tooltip and hover highlight behavior isn't shared across panels. + - **Shared crosshair** - When you hover the cursor over one graph panel in the dashboard, the crosshair is also displayed on all other graph panels in the dashboard. + - **Shared tooltip** - When you hover the cursor over one graph panel in the dashboard, the crosshair and tooltips are also displayed on all other graph panels in the dashboard. + +1. Click **Save dashboard**. +1. (Optional) Enter a description of the changes you've made. +1. Click **Save**. +1. Click **Exit edit**. + ## Add tags You can add metadata to your dashboards using tags. Tags also give you the ability to filter the list of dashboards. @@ -79,7 +93,7 @@ Tags can be up to 50 characters long, including spaces. To add tags to a dashboard, follow these steps: -1. On the **Settings** page, scroll down to the **Tags** section of the **General** tab. +1. On the the **General** tab of the **Settings** page, scroll down to the **Tags** section. 1. In the field, enter a new or existing tag. If you're entering an existing tag, make sure that you spell it the same way or a new tag is created. diff --git a/docs/sources/developers/http_api/authentication.md b/docs/sources/developers/http_api/authentication.md index 55b5bc46bf3..40ef698feb1 100644 --- a/docs/sources/developers/http_api/authentication.md +++ b/docs/sources/developers/http_api/authentication.md @@ -1,6 +1,6 @@ --- canonical: https://grafana.com/docs/grafana/latest/developers/http_api/authentication/ -description: 'You can authenticate HTTP API requests using basic authentication, a service account token, or a session cookie.' +description: 'You can authenticate HTTP API requests using basic authentication or a service account token.' keywords: - grafana - http diff --git a/docs/sources/observability-as-code/provision-resources/_index.md b/docs/sources/observability-as-code/provision-resources/_index.md index f6eb97af7be..d920f6d5009 100644 --- a/docs/sources/observability-as-code/provision-resources/_index.md +++ b/docs/sources/observability-as-code/provision-resources/_index.md @@ -23,52 +23,40 @@ Provisioning is an [experimental feature](https://grafana.com/docs/release-life- Sign up for Grafana Cloud Git Sync early access using [this form](https://forms.gle/WKkR3EVMcbqsNnkD9). {{< /admonition >}} -Using Provisioning, you can configure how to store your dashboard JSON files in either GitHub repositories using Git Sync or a local path. +Provisioning is an experimental feature that allows you to configure how to store your dashboard JSONs and other files in GitHub repositories using either Git Sync or a local path. -Of the two experimental options, Git Sync is the recommended method for provisioning your dashboards. You can synchronize any new dashboards and changes to existing dashboards to your configured GitHub repository. -If you push a change in the repository, those changes are mirrored in your Grafana instance. -For more information on configuring Git Sync, refer to [Set up Git Sync](https://grafana.com/docs/grafana//observability-as-code/provision-resources/git-sync-setup). +Of the two options, **Git Sync** is the favorited method for provisioning your dashboards. You can synchronize any new dashboards and changes to existing dashboards from the UI to your configured GitHub repository. If you push a change in the repository, those changes are mirrored in your Grafana instance. See [Git Sync workflow](#git-sync-workflow). -Refer to [Set up file provisioning](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup/) to learn more about the version of local file provisioning in Grafana 12. +Alternatively, **local file provisioning** allows you to include in your Grafana instance resources (such as folders and dashboard JSON files) that are stored in a local file system. See [Local file workflow](local-file-workflow). ## Provisioned folders and connections -Dashboards and folders saved to the local path are referred to as "provisioned" resources and are labeled as such in the Grafana UI. - -Dashboards saved in your GitHub repository or local folder configured appear in a provisioned folder in Grafana. +The dashboards saved in your GitHub repository or local folder appear in Grafana in the 'provisioned' folder. The dashboards and folders saved to the local path are referred to as 'provisioned' resources and are labeled as such in the Grafana UI. You can update any provisioned dashboard that is either stored within a GitHub repository (Git Sync workflow) or in a local file (local file workflow). You can set a single folder, or multiple folders to a different repository, with up to 10 connections. Alternatively, your entire Grafana instance can be the provisioned folder. -## How it works +## Git Sync workflow -A user decides to update a provisioned dashboard that is either stored within a GitHub repository (Git Sync workflow) or in a local file (local file workflow). +In the Git Sync workflow: -### Git Sync workflow +- When you provision resources with Git Sync you can modify them from within the Grafana UI or within the GitHub repository. Changes made in either the repository or the Grafana UI are bidirectional. +- Any changes made in the provisioned files stored in the GitHub repository are reflected in the Grafana database. By default, Grafana polls GitHub every 60 seconds. +- The Grafana UI reads from the database and updates the UI to reflect these changes. -Resources provisioned with Git Sync can be modified from within the Grafana UI or within the GitHub repository. -Changes made in either the repository or the Grafana UI are bidirectional. +For example, if you update a dashboard within the Grafana UI and click **Save** to preserve the changes, you'll be notified that the dashboard is provisioned in a GitHub repository. Next you'll be prompted to choose how to preserve the changes: either directly to a branch, or pushed to a new branch using a pull request in GitHub. -For example, when a user updates dashboards within the Grafana UI, they choose **Save** to preserve the changes. -Grafana notifies them that the dashboard is provisioned in a GitHub repository. -They choose how to preserve their changes: either saved directly to a branch or pushed to a new branch using a pull request in GitHub. -If they chose a new branch, then they open the pull request and follow their normal workflow. +For more information, see [Introduction to Git Sync](https://grafana.com/docs/grafana//observability-as-code/provision-resources/intro-git-sync) and [Set up Git Sync](https://grafana.com/docs/grafana//observability-as-code/provision-resources/git-sync-setup). -Grafana polls GitHub at a regular interval. -The connection is established using a personal access token for authorization. -With the webhooks feature enabled, repository notifications appear almost immediately. -Without webhooks, Grafana polls for changes at the specified interval. -The default polling interval is 60 seconds. +## Local file workflow -Any changes made in the provisioned files stored in the GitHub repository are reflected in the Grafana database. -The Grafana UI reads the database and updates the UI to reflect these changes. +In the local file workflow: -### Local file workflow +- All provisioned resources are changed in the local files. +- Any changes made in the provisioned files are reflected in the Grafana database. +- The Grafana UI reads the database and updates the UI to reflect these changes. +- You can't use the Grafana UI to edit or delete provisioned resources. -In the local file workflow, all provisioned resources are changed in the local files. -The user can't use the Grafana UI to edit or delete provisioned resources. - -Any changes made in the provisioned files are reflected in the Grafana database. -The Grafana UI reads the database and updates the UI to reflect these changes. +Learn more in [Set up file provisioning](https://grafana.com/docs/grafana//observability-as-code/provision-resources/file-path-setup/). ## Explore provisioning diff --git a/docs/sources/observability-as-code/provision-resources/file-path-setup.md b/docs/sources/observability-as-code/provision-resources/file-path-setup.md index b5e922456b6..1832e8ad001 100644 --- a/docs/sources/observability-as-code/provision-resources/file-path-setup.md +++ b/docs/sources/observability-as-code/provision-resources/file-path-setup.md @@ -22,7 +22,7 @@ Sign up for Grafana Cloud Git Sync early access using [this form](https://forms. {{< /admonition >}} -File provisioning in Grafana lets you include resources, including folders and dashboard JSON files, that are stored in a local file system. +Use local file provisioning to include in your Grafana instance resources (such as folders and dashboard JSON files) that are stored in a local file system. This page explains how to set up local file provisioning. @@ -48,7 +48,7 @@ Refer to [Provision Grafana](https://grafana.com/docs/grafana// ### Limitations - A provisioned dashboard can't be deleted from within Grafana UI. The dashboard has to be deleted at the local file system and those changes synced to Grafana. -- Changes from the local file system are one way: you can't save changes from +- Changes from the local file system are one way: you can't save changes from the UI to GitHub. ## Before you begin diff --git a/docs/sources/observability-as-code/provision-resources/intro-git-sync.md b/docs/sources/observability-as-code/provision-resources/intro-git-sync.md index 3ebcd0bd2e8..42bc9691264 100644 --- a/docs/sources/observability-as-code/provision-resources/intro-git-sync.md +++ b/docs/sources/observability-as-code/provision-resources/intro-git-sync.md @@ -13,7 +13,7 @@ title: Git Sync weight: 100 --- -# Git Sync +# Introduction to Git Sync {{< admonition type="caution" >}} Git Sync is an [experimental feature](https://grafana.com/docs/release-life-cycle/) introduced in Grafana v12 for open source and Enterprise editions. Engineering and on-call support is not available. Documentation is either limited or not provided outside of code comments. No SLA is provided. Enable the `provisioning` and `kubernetesDashboards` feature toggles in Grafana to use this feature. This feature is not publicly available in Grafana Cloud yet. Only the cloud-hosted version of GitHub (GitHub.com) is supported at this time. GitHub Enterprise is not yet compatible. diff --git a/docs/sources/observability-as-code/provision-resources/use-git-sync.md b/docs/sources/observability-as-code/provision-resources/use-git-sync.md index f54d43347dd..e8d323e578c 100644 --- a/docs/sources/observability-as-code/provision-resources/use-git-sync.md +++ b/docs/sources/observability-as-code/provision-resources/use-git-sync.md @@ -12,7 +12,7 @@ labels: - enterprise - oss title: Manage provisioned repositories with Git Sync -menuTitle: Manage repositories +menuTitle: Manage repositories with Git Sync weight: 400 --- @@ -25,8 +25,7 @@ Sign up for Grafana Cloud Git Sync early access using [this form](https://forms. {{< /admonition >}} -After you have set up Git Sync, you can synchronize dashboards and changes to existing dashboards to your configured GitHub repository. -If you push a change in the repository, those changes are mirrored in your Grafana instance. +After you have set up Git Sync, you can synchronize any changes in your existing dashboards with your configured GitHub repository. Similarly, if you push a change in the repository, those changes are mirrored in your Grafana instance. ## View current status of synchronization diff --git a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md b/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md index bb8de19264f..fda106213f9 100644 --- a/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md +++ b/docs/sources/panels-visualizations/query-transform-data/transform-data/index.md @@ -452,24 +452,28 @@ This transformation is very useful if your data source does not natively filter The available conditions for all fields are: -- **Regex** - Match a regex expression. - **Is Null** - Match if the value is null. - **Is Not Null** - Match if the value is not null. - **Equal** - Match if the value is equal to the specified value. -- **Different** - Match if the value is different than the specified value. +- **Not Equal** - Match if the value is not equal to the specified value. +- **Regex** - Match a regex expression. The available conditions for string fields are: - **Contains substring** - Match if the value contains the specified substring (case insensitive). - **Does not contain substring** - Match if the value doesn't contain the specified substring (case insensitive). -The available conditions for number and time fields are: +The available conditions for number fields are: - **Greater** - Match if the value is greater than the specified value. - **Lower** - Match if the value is lower than the specified value. - **Greater or equal** - Match if the value is greater or equal. - **Lower or equal** - Match if the value is lower or equal. -- **Range** - Match a range between a specified minimum and maximum, min and max included. A time field will pre-populate with variables to filter by selected time. +- **In between** - Match a range between a specified minimum and maximum, min and max included. + +The available conditions for time fields are: + +- **In between** - Match a range between a specified minimum and maximum. The min and max values will pre-populate with variables to filter by selected time. Consider the following dataset: diff --git a/docs/sources/panels-visualizations/visualizations/node-graph/index.md b/docs/sources/panels-visualizations/visualizations/node-graph/index.md index 577794bf65c..a18b7349310 100644 --- a/docs/sources/panels-visualizations/visualizations/node-graph/index.md +++ b/docs/sources/panels-visualizations/visualizations/node-graph/index.md @@ -238,6 +238,6 @@ Optional fields: | arc\_\_\* | number | Any field prefixed with `arc__` will be used to create the color circle around the node. All values in these fields should add up to 1. You can specify color using `config.color.fixedColor`. | | detail\_\_\* | string/number | Any field prefixed with `detail__` will be shown in the header of context menu when clicked on the node. Use `config.displayName` for more human readable label. | | color | string/number | Can be used to specify a single color instead of using the `arc__` fields to specify color sections. It can be either a string which should then be an acceptable HTML color string or it can be a number in which case the behavior depends on `field.config.color.mode` setting. This can be for example used to create gradient colors controlled by the field value. | -| icon | string | Name of the icon to show inside the node instead of the default stats. Only Grafana [built in icons](https://developers.grafana.com/ui/latest/index.html?path=/story/docs-overview-icon--icons-overview)) are allowed. | +| icon | string | Name of the icon to show inside the node instead of the default stats. Only Grafana [built in icons](https://developers.grafana.com/ui/latest/index.html?path=/story/iconography-icon--icons-overview)) are allowed. | | nodeRadius | number | Radius value in pixels. Used to manage node size. | | highlighted | boolean | Sets whether the node should be highlighted. Useful for example to represent a specific path in the graph by highlighting several nodes and edges. Default: `false` | diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 6a10893aae4..9702851cd19 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -726,6 +726,8 @@ Set to `true` if you host Grafana behind HTTPS. Default is `false`. Sets the `SameSite` cookie attribute and prevents the browser from sending this cookie along with cross-site requests. The main goal is to mitigate the risk of cross-origin information leakage. This setting also provides some protection against cross-site request forgery attacks (CSRF), [read more about SameSite here](https://owasp.org/www-community/SameSite). Valid values are `lax`, `strict`, `none`, and `disabled`. Default is `lax`. Using value `disabled` does not add any `SameSite` attribute to cookies. +If you want to use OAuth/SAML for login, it is necessary to configure this attribute as `lax`. + #### `allow_embedding` When `false`, the HTTP header `X-Frame-Options: deny` is set in Grafana HTTP responses which instructs browsers to not allow rendering Grafana in a ``, `