Compare commits

..

91 Commits

Author SHA1 Message Date
Larissa Wandzura
e206ef01af cleaned up the table a bit 2025-12-03 14:11:06 -06:00
Larissa Wandzura
06bbf043c0 minor change 2025-11-21 15:37:53 -06:00
Larissa Wandzura
079100b081 ran prettier 2025-11-20 11:19:15 -06:00
Larissa Wandzura
8e11703e94 more updates based on feedback 2025-11-19 17:29:47 -06:00
Larissa Wandzura
03e9ffcc91 Updates based on David's feedback 2025-11-19 16:54:36 -06:00
Larissa Wandzura
d7c75b8343 Update docs/sources/datasources/concepts.md
Co-authored-by: David Harris <david.harris@grafana.com>
2025-11-19 14:22:08 -06:00
Larissa Wandzura
9ddd8a02c0 Update docs/sources/datasources/concepts.md
Co-authored-by: David Harris <david.harris@grafana.com>
2025-11-19 14:21:35 -06:00
Larissa Wandzura
1601995fda Update docs/sources/datasources/concepts.md
Co-authored-by: David Harris <david.harris@grafana.com>
2025-11-19 14:21:23 -06:00
Larissa Wandzura
af87c3d6f3 update based on feedback 2025-11-14 15:14:38 -06:00
Larissa Wandzura
9f9b82f5cf clarified plugin install answer 2025-11-14 14:59:12 -06:00
Larissa Wandzura
04a9888a96 removed Grafana version to avoid confusing users 2025-11-13 16:17:32 -06:00
Larissa Wandzura
07a758d84a updates based on feedback on draft 2025-11-13 16:12:35 -06:00
Larissa Wandzura
111af8b1a8 Update docs/sources/datasources/concepts.md
Co-authored-by: Anna Urbiztondo <anna.urbiztondo@grafana.com>
2025-11-12 13:20:22 -06:00
Larissa Wandzura
4c97e49fc5 cleaned up spelling and punctuation 2025-11-07 16:10:32 -06:00
Larissa Wandzura
10a291ec8b added new concepts doc 2025-11-07 16:04:10 -06:00
beejeebus
0e9fe9dc40 Register external datasource plugins on startup
Current code only registers core datasource k8s api groups.

Add external plugins.

Companion grafana-enterprise PR:

https://github.com/grafana/grafana-enterprise/pull/10125
2025-11-07 14:42:41 -05:00
Paul Marbach
90ddd922ad Chore: Cleanup panelMonitoring feature flag (#113530) 2025-11-07 14:04:42 -05:00
Moustafa Baiou
1e1adafeec Alerting: Add admission hooks for rules app (#113429)
This adds validating admission hooks to enforce the requirements on AlertRules and RecordingRules that are currently enforced through the provisioning service and storage mechanisms in preparation of a consistent validation in both legacy storage and unified storage. It also adds a mutating admission hook to the app to ensure that folder annotations and folder labels are kept in sync so we can perform label-selector lists.
2025-11-07 12:01:16 -05:00
Paul Marbach
ecc9e9257e E2E: Prevent issue where certain times can cause test failures (#110196)
* E2E: Prevent issue where certain times can cause test failures

* re-enable first test
2025-11-07 11:34:11 -05:00
Paul Marbach
4fee8b34ad Suggestions: Refactor getPanelDataSummary into its own method (#113251)
* Suggestions: Refactor getPanelDataSummary into its own method

* restore order

* update some imports

* update codeowners
2025-11-07 11:33:13 -05:00
Roberto Jiménez Sánchez
02464c19b8 Provisioning: Add validation for Job specifications (#113590)
* Validate Job Specs

* Add comprehensive unit test coverage for job validator

- Added 8 new test cases to improve coverage from 88.9% to ~100%
- Tests for migrate action without options
- Tests for delete/move actions with resources (missing kind)
- Tests for move action with valid resources
- Tests for move/delete with both paths and resources
- Tests for move action with invalid source paths
- Tests for push action with valid paths

Now covers all validation paths including resource validation and
edge cases for all job action types.

* Add integration tests for job validation

Added comprehensive integration tests that verify the job validator properly
rejects invalid job specifications via the API:

- Test job without action (required field)
- Test job with invalid action
- Test pull job without pull options
- Test push job without push options
- Test push job with invalid branch name (consecutive dots)
- Test push job with path traversal attempt
- Test delete job without paths or resources
- Test delete job with invalid path (path traversal)
- Test move job without target path
- Test move job without paths or resources
- Test move job with invalid target path (path traversal)
- Test migrate job without migrate options
- Test valid pull job to ensure validation doesn't block legitimate requests

These tests verify that the admission controller properly validates job specs
before they are persisted, ensuring security (path traversal prevention) and
data integrity (required fields/options).

* Remove valid job test case from integration tests

Removed the positive test case as it's not necessary for validation testing.
The integration tests now focus solely on verifying that invalid job specs
are properly rejected by the admission controller.

* Fix movejob_test to expect validation error at creation time

Updated the 'move without target path' test to expect the job creation
to fail with a validation error, rather than expecting the job to be
created and then fail during execution.

This aligns with the new job validation logic which rejects invalid
job specs at the API admission control level (422 Unprocessable Entity)
before they can be persisted.

This is better behavior as it prevents invalid jobs from being created
in the first place, rather than allowing them to be created and then
failing during execution.

* Simplify action validation using slices.Contains

Replaced manual loop with slices.Contains for cleaner, more idiomatic Go code.
This reduces code complexity while maintaining the same validation logic.

- Added import for 'slices' package
- Replaced 8-line loop with 1-line slices.Contains call
- All unit tests pass

* Refactor job action validation in ValidateJob function

Removed the hardcoded valid actions array and simplified the validation logic. The function now directly appends an error for invalid actions, improving code clarity and maintainability. This change aligns with the recent updates to job validation, ensuring that invalid job specifications are properly handled.
2025-11-07 16:31:50 +00:00
Sven Grossmann
62129bb91f Search: Change copy to Search with Grafana Assistant (#113609) 2025-11-07 16:27:19 +00:00
Paul Marbach
3d8da61569 E2E: Improve ad-hoc filtering test (#113558)
* E2E: Improve ad-hoc filtering test

* remove unused import

* fix some table e2es after making getCell sync
2025-11-07 11:06:33 -05:00
Misi
d7d296df8e Fix: Return auth labels from /api/users/lookup (#113584)
* wip

* Return auth labels from /api/users/lookup

* Rename

* Address feedback

* Add more tests, fix tests

* Cleanup
2025-11-07 16:51:41 +01:00
Jean-Philippe Quéméner
305ed25896 fix(folders): add a circuit breaker to prevent infinite loops (#113596) 2025-11-07 14:32:17 +00:00
Yunwen Zheng
8b6cc211e9 Git Sync: Allow user disable push to configured branch (#113564)
* Git Sync: Allow user disable push to configured branch
2025-11-07 09:24:34 -05:00
Jean-Philippe Quéméner
1ca95cda4a fix(folders): prevent circular dependencies (#113595) 2025-11-07 14:19:55 +00:00
Alexa Vargas
e5ed003fb2 Dashboard Library: Add new "suggestedDashboards" feature toggle (#113591) 2025-11-07 13:38:59 +00:00
Jo
176b0f8b48 IAM: Refactor user org hooks to use MutateRequest API (#113392)
* update with mutation hooks

* add missing delete mutation
2025-11-07 14:36:53 +01:00
Juan Cabanas
33390a1483 LibraryPanels: Improve getAllLibraryElements filter performance (#113544) 2025-11-07 10:16:41 -03:00
Gabriel MABILLE
e90759e5af grafana-iam: enable dual writing for resource permissions (#112793)
* `grafana-iam`: enable dual writing for resource permissions

Co-authored-by: jguer <joao.guerreiro@grafana.com>

* copy paste mistake

* Reduce complexity

* nits to make the code easy to review

* Forgot to check the error

---------

Co-authored-by: jguer <joao.guerreiro@grafana.com>
2025-11-07 13:50:40 +01:00
Alex Khomenko
8cb5f5646a Provisioning: Fix miscellaneous issues with setting and displaying sync status (#113529)
* Provisioning: Preserve in progress job data

* Refactor code and cover more situations

* Fix linting

* Fix issue with remove path operation for started time

* Cleanup

* prettier

---------

Co-authored-by: Roberto Jimenez Sanchez <roberto.jimenez@grafana.com>
2025-11-07 12:27:25 +01:00
Seunghun Shin
c784de6ef5 Alerting: Add compressed periodic save for alert instances (#111803)
What is this feature?

This PR implements compressed periodic save for alert state storage, providing a more efficient alternative to regular periodic saves by grouping alert instances by rule UID and storing them using protobuf and snappy compression. When enabled via the state_compressed_periodic_save_enabled configuration option, the system groups alert instances by their alert rule, compresses each group using protobuf serialization and snappy compression, and processes all rules within a single database transaction at specified intervals instead of syncing after every alert evaluation cycle.

Why do we need this feature?

During discussions in PR #111357, we identified the need for a compressed approach to periodic alert state storage that could further reduce database load beyond the jitter mechanism. While the jitter feature distributes database operations over time, this compressed periodic save approach reduces the frequency of database operations by batching alert state updates at explicitly declared intervals rather than syncing after every alert evaluation cycle.
This approach provides several key benefits:

- Reduced Database Frequency: Instead of frequent sync operations tied to alert evaluation cycles, updates occur only at configured intervals
- Storage Efficiency: Rule-based grouping with protobuf and snappy compression significantly reduces storage requirements

The compressed periodic save complements the existing jitter mechanism by providing an alternative strategy focused on reducing overall database interaction frequency while maintaining data integrity through compression and batching.

Who is this feature for?

- Platform/Infrastructure teams managing large-scale Grafana deployments with high alert cardinality
- Organizations looking to optimize storage costs and database performance for alerting workloads
- Production environments with 1000+ alert rules where database write frequency is a concern
2025-11-07 11:51:48 +01:00
Jean-Philippe Quéméner
589435b7c2 fix(unified-storage): resource server tracing (#113582) 2025-11-07 11:51:32 +01:00
Gilles De Mey
b4d2d1eaf5 Alerting: Fix width of the code editor for Alertmanager configurations (#113541)
fix width of the code editor for Alertmanager configurations
2025-11-07 11:15:18 +01:00
Tobias Skarhed
36e28963d3 Scopes: Script for setting up gdev scope resources (#113448)
* Script for setting up gdev scope objects

* Script for setting up gdev scope objects

* Format

* Update codeowners

* Do a feature flag check

* Formatting

* Remove FF check, because creation is explicit anyways

* Formatting
2025-11-07 10:56:16 +01:00
Ida Štambuk
942b847952 CloudWatch: Add anomaly command to language support, add documentation for anomaly queries (#113311) 2025-11-07 09:54:24 +00:00
Elliot Kirk
488423abfc Icons: add hand pointer icon (#113255)
add hand pointer icon
2025-11-07 09:53:42 +00:00
Roberto Jiménez Sánchez
f75c853b90 Provisioning: Update slog-gokit to v0.1.5 to fix data race (#113455)
* Use fork of slog-gokit to fix data race

Replace github.com/tjhop/slog-gokit with fork that includes fix for data race in handler.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Update workspace

* Bump github.com/tjhop/slog-gokit to v0.1.5

* Update go.sum

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-11-07 09:47:53 +00:00
Ida Štambuk
4bbbd19049 CloudWatch: Make match exact toggle false by default (#113314) 2025-11-07 10:30:24 +01:00
Nathan Vērzemnieks
f4b23253b1 DataSources: Update SDKs in support of auth service (#112101)
* DataSources: Update SDKs for auth service

* Fix deprecated methods & types for new arrow-go version
2025-11-07 10:15:27 +01:00
Erik Sundell
06e1c83276 Chore: Bump plugin-e2e (#113578)
* bump plugin-e2e

* use plugin-e2e selector

* update lock file
2025-11-07 10:11:05 +01:00
Moustafa Baiou
54041155bd fix import path for annotation app 2025-11-06 19:33:12 -05:00
Adam Yeats
b9b1028b91 Elasticsearch: Handle keyed filters buckets and emit frames (#113478) 2025-11-06 17:20:08 -06:00
Taygun Bulmus
f468597ad8 Document rule_version_record_limit setting (#113511)
* Document rule_version_record_limit setting

Add documentation for rule_version_record_limit configuration.

* Update docs/sources/setup-grafana/configure-grafana/_index.md

Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com>

* Update docs/sources/setup-grafana/configure-grafana/_index.md

* run prettier

---------

Co-authored-by: Jacob Valdez <jacob.valdez@grafana.com>
2025-11-06 15:10:25 -06:00
Kevin Yu
69060f5437 CloudWatch Logs: Limit CloudWatch logs queries to use logGroupIdentifiers only for monitoring accounts (#113137)
* Set the log group name when executing log queries from the frontend

* Add helper for a data source instance to check if its a monitoring account

* Execute log queries with log group identifiers only for monitoring account queries

* fix cloudwatch datasource.ts tests

* remove unneeded check
2025-11-06 12:19:39 -08:00
Leon Sorokin
2ac4d0a13e Chore: Remove Intl.DurationFormat polyfill from runtime (#113475) 2025-11-06 20:11:53 +00:00
Matias Chomicki
e953e76006 Log Details: Dedicated context provider + improvements (#113409)
* LogDetailsContext: create component

* LogListContext: extract details out of context

* Refactor components to use new context provider

* More component updates

* Update currentLog implementation

* Use new context provider

* LogLineDetails: prevent cascade of listeners

* LogDetailsContext: sync currentLog with changes

* LogLine: use icon status to show the current log

* LogLineDetails: first tab is the last open log line

* LogLineDetailsLog: respect font size

* Update tests

* Update tests

* LogList: add integration test

* LogLine: use level to mark the current log

* Chore: only check uids, no need for references

* Fix duplicated hook usage

* chore: overflow auto

* LogList: consider field selector width

* Revert "LogLine: use level to mark the current log"

This reverts commit 2d5d54d9a7.

* LogLine: darken details displayed, font weight bold current

* LogLineMenu: icon when current log

* Differenciate contrast from light and dark themes

* Use angle-right for the active icon
2025-11-06 20:21:51 +01:00
Will Assis
c9e4c26c11 unified-storage: add more list pagination tests (#113543)
* unified-storage: add more list pagination tests
2025-11-06 13:36:02 -05:00
Serge Zaitsev
95ea758475 Chore: Start annotations app (#113018)
* annotation legacy store with api server, read only

* Add a feature flag for annotations app

* implement list filters

* annotations are not addressable by ID for read operations

* fix registry apps test

* add ownership for an app

* disable linter

* typo, of course

* fix go workspace

* update workspace

* copy annotation app in dockerfile

* update workspace

---------

Co-authored-by: Tania B. <10127682+undef1nd@users.noreply.github.com>
2025-11-06 19:22:20 +01:00
Rafael Bortolon Paulovic
0931423259 fix: use step output instead of !cancelled() in condition (#113533)
* fix: use step output instead of !cancelled() in condition

We are uploading profile files without need otherwise

* fix: try using !cancelled() and output

Otherwise, step isn't triggered due to failure of the previous one
2025-11-06 19:17:05 +01:00
Rafael Bortolon Paulovic
75afc64dd0 fix: disable mode4,5 tests with library element table (#113539) 2025-11-06 17:49:44 +00:00
Ihor Yeromin
67ca3c231a Test: Fix array element removal in adhoc filter e2e test (#113514)
fix(test): adhoc-filter-from-panel
2025-11-06 18:46:23 +01:00
Adela Almasan
bcc2057456 Canvas: Fix Field image source when non-string field is used (#113534) 2025-11-06 11:01:25 -06:00
Konrad Lalik
720dfb65be Alerting: Alerts page performance improvements (#113391)
* Add IntersectionObserver to render rule viz panel only when visible

* Move data transformations to dataTransform file

* Add tests for data transform

* Use a new time series transformation algorithm

* Reduce the number of time series data conversion

* Memoize small components

* Update tests

* Remove some comments

* Use Box component for styling. Remove unnecessary effect dependency
2025-11-06 17:39:27 +01:00
Mihai Turdean
7df3582237 Authz: Implement Query operation for Zanzana with folder parent retrieval (#113483) 2025-11-06 09:06:42 -07:00
Irene Rodríguez
2e0cf9bb61 Update grafanacli-workflows.md with command link (#113527)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-06 17:02:50 +01:00
Yunwen Zheng
0ed742cad7 FilesView: Git sync repository -> Files list remove size column, added unit tests (#113425)
* FilesView: remove size column, added unit tests
2025-11-06 15:44:46 +00:00
Konrad Lalik
6746207c36 Alerting: Fix url rule form parsing (#113513)
Fix alertRuleFormSchema to properly handle query model objects
2025-11-06 16:43:42 +01:00
Alex Khomenko
4430699f2d Provisioning: Update recent jobs (#113509)
* Provisioning: refactor recent jobs

* Re-render only on initial load

* i18n

* Refactor init loading

* Update spinner

* upd
2025-11-06 17:35:23 +02:00
Gábor Farkas
acb0320796 datasources: apiserver: do not enable extra methods by default (#113395) 2025-11-06 15:34:32 +01:00
Ryan McKinley
95ffd1a55a LibraryPanel: Cleanup service calls (#113277)
* cleanup

* library panel via search

* test cleanup

* merge main

* add FindDashboards mock

* no matching dashbaords should return empty

* do not alllow name and libraryPanel query
2025-11-06 15:31:02 +01:00
linoman
ca5d898120 SCIM: Upgrade the User.UID field to allow for the new scim- prefix (#113500)
Upgrade the User.UID field to allow for the new scim- prefix
2025-11-06 15:26:18 +01:00
Roberto Jiménez Sánchez
e3d73ddb81 Bump nanogit version with delta resolution fixes (#113516)
* Bump nanogit version with delta fixes

* Update workspace
2025-11-06 15:12:00 +01:00
Rafael Bortolon Paulovic
e69f3c55f7 fix: delete folders using postorder (#113493)
* fix: delete folders using postorder

* chore: use helper function and do not add method to Folder store

- addresses other review comments fixing log messages and cleans up the unit tests

* chore: run library element tests on modes 2,3,5 only

* chore: adjust to folder.SortByPostorder(folders []*Folder)

* chore: run library panels tests in mode 2,3,5 only

* chore: run tests in all modes and increase timeout

- adjusting the modes and tweaking configs will be done separately
2025-11-06 15:04:34 +01:00
Tom Ratcliffe
fbf1cdd0ce Folders: Remove unneeded reducer (#113506) 2025-11-06 13:46:41 +00:00
Haris Rozajac
859865351d Dashboard Export: Don't pass already templateized ds var to ds service (#113319) 2025-11-06 06:42:21 -07:00
Rafael Bortolon Paulovic
7b3145a3c1 fix: delete subfolder dangling panels (#113419)
* fix: delete subfolder dangling panels and error if used

* chore: add observation about library panel DeleteInFolders

- logs folders UIDs on DeleteInFolders error

* chore: add integration test for blocking library panel deletion and handling dangling library panels

* chore: fix integration test on mode 4 and 5
2025-11-06 13:56:32 +01:00
Jean-Philippe Quéméner
fd14d4a5ed feat(unified-storage): add tracing to dual writer and legacy storage (#113504)
Co-authored-by: Mustafa Sencer Özcan <32759850+mustafasencer@users.noreply.github.com>
2025-11-06 11:42:46 +00:00
Leon Sorokin
efd6b250d9 Chore: Remove Chance.js dependency from runtime code (#113457) 2025-11-06 05:08:31 -06:00
renovate-sh-app[bot]
6bf5e3303e chore(deps): pin dependencies (#113494)
Signed-off-by: renovate-sh-app[bot] <219655108+renovate-sh-app[bot]@users.noreply.github.com>
Co-authored-by: renovate-sh-app[bot] <219655108+renovate-sh-app[bot]@users.noreply.github.com>
2025-11-06 11:04:40 +00:00
Sergej-Vlasov
71d511abd8 VariableControls: Adjust variable selection in edit mode (#113408)
* adjust variable selection logic

* clean up

* adjust attribute used
2025-11-06 10:27:26 +00:00
Esteban Beltran
1b278cc87e Plugins: cleanup feature toggle pluginsFrontendSandbox (#113439)
* clean feature pluginsFrontendSandbox

Co-authored-by: Andres Martinez Gotor <andres.martinez@grafana.com>
2025-11-06 11:06:53 +01:00
Mustafa Sencer Özcan
51c31e00a4 fix: dual writer log object formatting (#113492)
fix: logging
2025-11-06 11:03:28 +01:00
Gabriel MABILLE
ff53276870 grafana-iam: Instantiate ExternalGroupMappingStorage as a NoopStorage (#113499)
Co-authored-by: jguer <joao.guerreiro@grafana.com>
2025-11-06 11:00:37 +01:00
Tom Ratcliffe
b739e4e802 APIs: Include enterprise spec check (#113470) 2025-11-06 08:31:24 +00:00
Rafael Bortolon Paulovic
7281bb7069 fix: background delete on create failure after ctx cancellation (#113442)
* fix: background delete on create failure after ctx cancellation

* fix: address comments

* chore: remove tests using mock
2025-11-06 09:21:11 +01:00
Andres Martinez Gotor
35ac04bad3 Chore: Fix two minor bugs related to favorite datasources (#113444) 2025-11-06 09:18:11 +01:00
grafana-pr-automation[bot]
2411e78cd1 I18n: Download translations from Crowdin (#113428)
New Crowdin translations by GitHub Action

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-11-06 00:39:34 +00:00
grafana-delivery-bot[bot]
7236ba5fce Release: Bump version to 12.4.0-pre (#113480)
bump version 12.4.0-pre

Co-authored-by: grafana-delivery-bot[bot] <grafana-delivery-bot[bot]@users.noreply.github.com>
2025-11-05 23:54:05 +01:00
colin-stuart
612a0d1c7f Revert "SCIM: Update UIDs for provisioned users (#113423)" (#113474)
This reverts commit daa28773d6.
2025-11-05 15:49:05 -06:00
Misi
06373ae47b IAM: Add ExternalGroupMapping kind for TeamSync (#113052)
* wip

* wip

* Add authorizer -> VERIFY it's working correctly

* Update openapi definitions

* Authorizer wip

* regen apis

* Increase timeout of pg int tests to 20m

* Revert "Increase timeout of pg int tests to 20m"

This reverts commit 8c20568217.

* Fix NewTestStore when Truncate is enabled
2025-11-05 18:02:34 +01:00
linoman
daa28773d6 SCIM: Update UIDs for provisioned users (#113423)
* Update UIDs for provisioned users

* change the prefix from scim_ to scim-

* Update tests
2025-11-05 17:52:23 +01:00
Ivana Huckova
1830e2ce9d CommanPalette: Add Assistant integration for empty state (#112601)
* CommanPalette: Add Assistant integration for empty state

* Update assistant package and use new onClick pop

* i18n

* Update public/locales/en-US/grafana.json

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>

* Update public/app/features/commandPalette/CommandPalette.tsx

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>

* Update test

---------

Co-authored-by: Sven Grossmann <sven.grossmann@grafana.com>
2025-11-05 16:13:47 +01:00
Sven Grossmann
9a8d17a209 Toolbar: Move ExtensionSidebar next to toolbar (#113404)
* Toolbar: Move ExtensionSidebar next to toolbar

* Toolbar: Simplify AppChrome

* simplify height calculation

* Fix scrollbar positon

* remove irrelevant comment

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>

---------

Co-authored-by: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com>
2025-11-05 16:05:20 +01:00
Galen Kistler
8149f586b3 Annotations: Multi-lane annotations rendering (lane per frame) (#111559)
* feat: support multi-lane annotations panel option

---------

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
2025-11-05 09:00:28 -06:00
Andre Pereira
c7dd159db1 Trace View: Span tree style updates (#113095)
* Style tweaks of span bar row

* More tweaks

* More tweaks to span style

* 2px width for the service color border

* Refactor SpanBarRow to function component

* Refactor SpanDetailRow to function component

* Refactor SpanTreeOffset to function component

* eslint ignore

* Service name font weight to 500

* Don't render the last indentGuide if the span doens't have children

* Fix tests
2025-11-05 14:49:47 +00:00
Yunwen Zheng
a17d5a75fe FolderActionsButton: Provisioned folder should hide "Manage Permission" folder action (#113367)
* FolderActionsButton: Hide Manage permission option when folder is git provisioned
2025-11-05 08:59:26 -05:00
Yunwen Zheng
2ccb7f618d ConfigFormGithubCollapse: Hide github features section if nothing is available (#113410)
ConfigFormGithubCollapse: Hide github features section if nothing is available. Added unit tests
2025-11-05 08:59:07 -05:00
Mustafa Sencer Özcan
b97fb638ad fix: only validate allowed descendants for folder deletion (#113440) 2025-11-05 14:04:43 +01:00
Bogdan Matei
d9d227ec4d Dashboard: Hide add variable button in edit views (#113438) 2025-11-05 14:38:31 +02:00
416 changed files with 17860 additions and 4468 deletions

4
.github/CODEOWNERS vendored
View File

@@ -98,6 +98,7 @@
/apps/correlations @grafana/datapro
/apps/example/ @grafana/grafana-app-platform-squad
/apps/logsdrilldown/ @grafana/observability-logs
/apps/annotation/ @grafana/grafana-backend-services-squad
/pkg/api/ @grafana/grafana-backend-group
/pkg/apis/ @grafana/grafana-app-platform-squad
/pkg/apis/query @grafana/grafana-datasources-core-services
@@ -226,6 +227,7 @@
/devenv/datasources.yaml @grafana/grafana-backend-group
/devenv/datasources_docker.yaml @grafana/grafana-backend-group
/devenv/dev-dashboards-without-uid/ @grafana/dashboards-squad
/devenv/scopes/ @grafana/grafana-operator-experience-squad
/devenv/dev-dashboards/annotations @grafana/dataviz-squad
/devenv/dev-dashboards/migrations @grafana/dataviz-squad
@@ -252,7 +254,6 @@
/devenv/dev-dashboards/all-panels.json @grafana/dataviz-squad
/devenv/dev-dashboards/dashboards.go @grafana/dataviz-squad
/devenv/dev-dashboards/home.json @grafana/dataviz-squad
/devenv/dev-dashboards/datasource-elasticsearch/ @grafana/partner-datasources
/devenv/dev-dashboards/datasource-opentsdb/ @grafana/partner-datasources
/devenv/dev-dashboards/datasource-influxdb/ @grafana/partner-datasources
@@ -548,6 +549,7 @@ i18next.config.ts @grafana/grafana-frontend-platform
/packages/grafana-data/src/geo/ @grafana/dataviz-squad
/packages/grafana-data/src/monaco/ @grafana/partner-datasources
/packages/grafana-data/src/panel/ @grafana/dashboards-squad
/packages/grafana-data/src/panel/suggestions/ @grafana/dataviz-squad
/packages/grafana-data/src/query/ @grafana/grafana-datasources-core-services
/packages/grafana-data/src/rbac/ @grafana/access-squad
/packages/grafana-data/src/table/ @grafana/dataviz-squad

View File

@@ -79,7 +79,7 @@ jobs:
make swagger-clean && make openapi3-gen
# Check if the generated specs differ from what's in the repository
for f in public/api-merged.json public/openapi3.json; do git add $f; done
for f in public/api-merged.json public/openapi3.json public/api-enterprise-spec.json; do git add $f; done
if [ -z "$(git diff --name-only --cached)" ]; then
echo "OpenAPI specs are up to date!"
else

View File

@@ -182,7 +182,7 @@ jobs:
with:
persist-credentials: false
- name: Setup Docker
uses: docker/setup-docker-action@efe9e3891a4f7307e689f2100b33a155b900a608 # v4
uses: docker/setup-docker-action@3fb92d6d9c634363128c8cce4bc3b2826526370a # v4
- name: Setup Node.js
uses: ./.github/actions/setup-node
- name: Install Tilt

View File

@@ -34,6 +34,6 @@ jobs:
uses: actions/checkout@v5
with:
persist-credentials: false
- uses: docker/setup-docker-action@efe9e3891a4f7307e689f2100b33a155b900a608 # v4
- uses: docker/setup-docker-action@3fb92d6d9c634363128c8cce4bc3b2826526370a # v4
- name: Build Dockerfile
run: make build-docker-full

View File

@@ -68,7 +68,7 @@ jobs:
run: |
set -euo pipefail
readarray -t PACKAGES <<< "$(./scripts/ci/backend-tests/pkgs-with-tests-named.sh -b TestIntegration | ./scripts/ci/backend-tests/shard.sh -N"$SHARD" -d-)"
go test -tags=sqlite -timeout=8m -run '^TestIntegration' "${PACKAGES[@]}"
go test -tags=sqlite -timeout=12m -run '^TestIntegration' "${PACKAGES[@]}"
sqlite_nocgo:
needs: detect-changes
@@ -109,7 +109,7 @@ jobs:
# Build regex pattern like: pkg1$|pkg2$|pkg3$
SKIP_PATTERN=$(echo "$SKIP_PACKAGES" | sed '/^$/d' | sed 's|.*|&$|' | paste -sd '|' -)
readarray -t PACKAGES <<< "$(./scripts/ci/backend-tests/pkgs-with-tests-named.sh -b TestIntegration | ./scripts/ci/backend-tests/shard.sh -N "$SHARD" -d - | grep -Ev "($SKIP_PATTERN)")"
go test -tags=sqlite -timeout=8m -run '^TestIntegration' "${PACKAGES[@]}"
go test -tags=sqlite -timeout=12m -run '^TestIntegration' "${PACKAGES[@]}"
- name: Run profiled tests
id: run-profiled-tests
if: matrix.shard == 'profiled'
@@ -135,7 +135,7 @@ jobs:
pkg_name=$(basename "$full_pkg" | tr '/' '_' | tr '.' '_')
echo "📦 Running $full_pkg"
set +e
go test -tags=sqlite -timeout=8m -run '^TestIntegration' \
go test -tags=sqlite -timeout=12m -run '^TestIntegration' \
-outputdir=profiles \
-cpuprofile="cpu_${pkg_name}.prof" \
-memprofile="mem_${pkg_name}.prof" \
@@ -150,10 +150,16 @@ jobs:
echo "✅ $full_pkg passed"
fi
done
# Set output for artifact upload
if [ $EXIT_CODE -ne 0 ]; then
echo "upload_artifacts=true" >> "$GITHUB_OUTPUT"
else
echo "upload_artifacts=false" >> "$GITHUB_OUTPUT"
fi
exit $EXIT_CODE
- name: Output test profiles and traces
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v4
if: (matrix.shard == 'profiled' && !cancelled())
if: matrix.shard == 'profiled' && !cancelled() && steps.run-profiled-tests.outputs.upload_artifacts == 'true'
with:
name: integration-test-profiles-sqlite-nocgo-${{ github.run_number }}
path: profiles/

View File

@@ -95,6 +95,7 @@ COPY pkg/aggregator pkg/aggregator
COPY apps/playlist apps/playlist
COPY apps/plugins apps/plugins
COPY apps/shorturl apps/shorturl
COPY apps/annotation apps/annotation
COPY apps/correlations apps/correlations
COPY apps/preferences apps/preferences
COPY apps/provisioning apps/provisioning

View File

@@ -68,13 +68,13 @@ require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/at-wat/mqtt-go v0.19.4 // indirect
github.com/aws/aws-sdk-go v1.55.7 // indirect
github.com/aws/aws-sdk-go-v2 v1.38.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.6 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 // indirect
github.com/aws/aws-sdk-go-v2 v1.39.1 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.14 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect
github.com/aws/smithy-go v1.23.1 // indirect
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect
github.com/benbjohnson/clock v1.3.5 // indirect
@@ -91,7 +91,6 @@ require (
github.com/cloudflare/circl v1.6.1 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/diegoholiveira/jsonlogic/v3 v3.7.4 // indirect
@@ -114,7 +113,7 @@ require (
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-ldap/ldap/v3 v3.4.4 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.24.0 // indirect
@@ -159,7 +158,7 @@ require (
github.com/grafana/authlib v0.0.0-20250930082137-a40e2c2b094f // 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
github.com/grafana/grafana-aws-sdk v1.3.0 // indirect
github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // indirect
github.com/grafana/grafana/apps/plugins v0.0.0 // indirect
github.com/grafana/grafana/apps/provisioning v0.0.0 // indirect
@@ -199,7 +198,6 @@ require (
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lestrrat-go/strftime v1.0.4 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattetti/filebuffer v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
@@ -252,7 +250,6 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect
@@ -265,11 +262,7 @@ require (
github.com/stretchr/objx v0.5.2 // indirect
github.com/tetratelabs/wazero v1.8.2 // indirect
github.com/thomaspoignant/go-feature-flag v1.42.0 // indirect
github.com/tjhop/slog-gokit v0.1.3 // indirect
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect
github.com/urfave/cli v1.22.17 // indirect
github.com/tjhop/slog-gokit v0.1.5 // indirect
github.com/woodsbury/decimal128 v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/zeebo/xxh3 v1.0.2 // indirect
@@ -319,7 +312,6 @@ require (
google.golang.org/protobuf v1.36.10 // 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
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/mail.v2 v2.3.1 // indirect

View File

@@ -173,42 +173,42 @@ github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8=
github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE=
github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
github.com/aws/aws-sdk-go-v2/config v1.31.2 h1:NOaSZpVGEH2Np/c1toSeW0jooNl+9ALmsUTZ8YvkJR0=
github.com/aws/aws-sdk-go-v2/config v1.31.2/go.mod h1:17ft42Yb2lF6OigqSYiDAiUcX4RIkEMY6XxEMJsrAes=
github.com/aws/aws-sdk-go-v2/credentials v1.18.6 h1:AmmvNEYrru7sYNJnp3pf57lGbiarX4T9qU/6AZ9SucU=
github.com/aws/aws-sdk-go-v2/credentials v1.18.6/go.mod h1:/jdQkh1iVPa01xndfECInp1v1Wnp70v3K4MvtlLGVEc=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 h1:lpdMwTzmuDLkgW7086jE94HweHCqG+uOJwHf3LZs7T0=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY=
github.com/aws/aws-sdk-go-v2/config v1.31.10 h1:7LllDZAegXU3yk41mwM6KcPu0wmjKGQB1bg99bNdQm4=
github.com/aws/aws-sdk-go-v2/config v1.31.10/go.mod h1:Ge6gzXPjqu4v0oHvgAwvGzYcK921GU0hQM25WF/Kl+8=
github.com/aws/aws-sdk-go-v2/credentials v1.18.14 h1:TxkI7QI+sFkTItN/6cJuMZEIVMFXeu2dI1ZffkXngKI=
github.com/aws/aws-sdk-go-v2/credentials v1.18.14/go.mod h1:12x4Uw/vijC11XkctTjy92TNCQ+UnNJkT7fzX0Yd93E=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8 h1:gLD09eaJUdiszm7vd1btiQUYE0Hj+0I2b8AS+75z9AY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8/go.mod h1:4RW3oMPt1POR74qVOC4SbubxAwdP4pCT0nSw3jycOU4=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 h1:cTXRdLkpBanlDwISl+5chq5ui1d1YWg4PWMR9c3kXyw=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84/go.mod h1:kwSy5X7tfIHN39uucmjQVs2LvDdXEjQucgQQEqCggEo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 h1:IdCLsiiIj5YJ3AFevsewURCPV+YWUlOW8JiPhoAy8vg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 h1:j7vjtr1YIssWQOMeOWRbh3z8g2oY/xPjnZH2gLY4sGw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8/go.mod h1:KcGkXFVU8U28qS4KvLEcPxytPZPBcRawaH2Pf/0jptE=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8/go.mod h1:JnA+hPWeYAVbDssp83tv+ysAG8lTfLVXvSsyKg/7xNA=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0NacDMnaC+68za9jLwkjzxGWm0KB5HTUHjLTI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8/go.mod h1:Fw+MyTwlwjFsSTE31mH211Np+CUslml8mzc0AFEG09s=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 h1:0reDqfEN+tB+sozj2r92Bep8MEwBZgtAXTND1Kk9OXg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 h1:ve9dYBB8CfJGTFqcQ3ZLAAb/KXWgYlgu/2R2TZL2Ko0=
github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 h1:pd9G9HQaM6UZAZh19pYOkpKSQkyQQ9ftnl/LttQOcGI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.4 h1:FTdEN9dtWPB0EOURNtDPmwGp6GGvMqRJCAihkSl/1No=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.4/go.mod h1:mYubxV9Ff42fZH4kexj43gFPhgc/LyC7KqvUKt1watc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 h1:I7ghctfGXrscr7r1Ga/mDqSJKm7Fkpl5Mwq79Z+rZqU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0/go.mod h1:Zo9id81XP6jbayIFWNuDpA6lMBWhsVy+3ou2jLa4JnA=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 h1:+LVB0xBqEgjQoqr9bGZbRzvg212B0f17JdflleJRNR4=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5/go.mod h1:xoaxeqnnUaZjPjaICgIy5B+MHCSb/ZSOn4MvkFNOUA0=
github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M=
github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 h1:60m4tnanN1ctzIu4V3bfCNJ39BiOPSm1gHFlFjTkRE0=
@@ -334,10 +334,7 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8=
github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg=
@@ -456,8 +453,8 @@ github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXg
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
@@ -663,9 +660,6 @@ github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -689,8 +683,8 @@ github.com/grafana/grafana-app-sdk v0.48.1 h1:bKJadWH18WCpJ+Zk8AezRFXCcZgGredRv+
github.com/grafana/grafana-app-sdk v0.48.1/go.mod h1:5LljCz+wvmGfkQ8ZKTOfserhtXNEF0cSFthoWShvN6c=
github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng=
github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
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-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM=
github.com/grafana/grafana-aws-sdk v1.3.0/go.mod h1:VGycF0JkCGKND2O5je1ucOqPJ0ZNhZYzV3c2bNBAaGk=
github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk=
github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1/go.mod h1:Oi4anANlCuTCc66jCyqIzfVbgLXFll8Wja+Y4vfANlc=
github.com/grafana/grafana-plugin-sdk-go v0.281.0 h1:V8dGyatzcOLQeivFhBV2JWMwTSZH/clDnpfKG9p3dTA=
@@ -833,9 +827,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ=
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
@@ -882,8 +873,6 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/madflojo/testcerts v1.4.0 h1:I09gN0C1ly9IgeVNcAqKk8RAKIJTe3QnFrrPBDyvzN4=
github.com/madflojo/testcerts v1.4.0/go.mod h1:MW8sh39gLnkKh4K0Nc55AyHEDl9l/FBLDUsQhpmkuo0=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
@@ -1115,8 +1104,6 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
@@ -1132,7 +1119,6 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 h1:OfRzdxCzDhp+rsKWXuOO2I/quKMJ/+TQwVbIP/gltZg=
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92/go.mod h1:7/OT02F6S6I7v6WXb+IjhMuZEYfH/RJ5RwEWnEo5BMg=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -1141,10 +1127,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
@@ -1189,7 +1171,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
@@ -1202,8 +1183,8 @@ github.com/thejerf/slogassert v0.3.4/go.mod h1:0zn9ISLVKo1aPMTqcGfG1o6dWwt+Rk574
github.com/thomaspoignant/go-feature-flag v1.42.0 h1:C7embmOTzaLyRki+OoU2RvtVjJE9IrvgBA2C1mRN1lc=
github.com/thomaspoignant/go-feature-flag v1.42.0/go.mod h1:y0QiWH7chHWhGATb/+XqwAwErORmPSH2MUsQlCmmWlM=
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o=
github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs=
github.com/tjhop/slog-gokit v0.1.5 h1:ayloIUi5EK2QYB8eY4DOPO95/mRtMW42lUkp3quJohc=
github.com/tjhop/slog-gokit v0.1.5/go.mod h1:yA48zAHvV+Sg4z4VRyeFyFUNNXd3JY5Zg84u3USICq0=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
@@ -1213,16 +1194,6 @@ github.com/uber/jaeger-lib v2.4.1+incompatible h1:td4jdvLcExb4cBISKIpHuGoVXh+dVK
github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI=
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4=
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU=
github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a h1:vcrhXnj9g9PIE+cmZgaPSwOyJ8MAQTRmsgGrB0x5rF4=
github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
github.com/wk8/go-ordered-map v1.0.0 h1:BV7z+2PaK8LTSd/mWgY12HyMAo5CEgkHqbkVq2thqr8=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
@@ -1528,7 +1499,6 @@ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -1892,8 +1862,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo=
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=

View File

@@ -8,7 +8,16 @@ spec:
preferredVersion: v0alpha1
versions:
- kinds:
- conversion: false
- admission:
mutation:
operations:
- CREATE
- UPDATE
validation:
operations:
- CREATE
- UPDATE
conversion: false
kind: AlertRule
plural: AlertRules
schemas:
@@ -214,7 +223,16 @@ spec:
- spec.panelRef.dashboardUID
- spec.panelRef.panelID
- spec.notificationSettings.receiver
- conversion: false
- admission:
mutation:
operations:
- CREATE
- UPDATE
validation:
operations:
- CREATE
- UPDATE
conversion: false
kind: RecordingRule
plural: RecordingRules
schemas:

View File

@@ -5,6 +5,7 @@ go 1.25.3
require (
github.com/grafana/grafana-app-sdk v0.48.1
github.com/grafana/grafana-app-sdk/logging v0.48.1
github.com/prometheus/common v0.67.1
k8s.io/apimachinery v0.34.1
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912
)
@@ -49,7 +50,6 @@ require (
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect

View File

@@ -13,6 +13,18 @@ alertRulev0alpha1: alertRuleKind & {
schema: {
spec: v0alpha1.AlertRuleSpec
}
validation: {
operations: [
"CREATE",
"UPDATE",
]
}
mutation: {
operations: [
"CREATE",
"UPDATE",
]
}
selectableFields: [
"spec.title",
"spec.paused",

View File

@@ -13,6 +13,18 @@ recordingRulev0alpha1: recordingRuleKind & {
schema: {
spec: v0alpha1.RecordingRuleSpec
}
validation: {
operations: [
"CREATE",
"UPDATE",
]
}
mutation: {
operations: [
"CREATE",
"UPDATE",
]
}
selectableFields: [
"spec.title",
"spec.paused",

View File

@@ -3,6 +3,7 @@ package v0alpha1
import (
"fmt"
"slices"
"time"
)
func (o *AlertRule) GetProvenanceStatus() string {
@@ -48,4 +49,78 @@ func (s *AlertRuleSpec) ExecErrStateOrDefault() string {
return s.ExecErrState
}
// TODO: add duration clamping for the field types AlertRulePromDuration, AlertRulePromDurationWMillis, and the For and KeepFiringFor string pointers
func (d *AlertRulePromDuration) ToDuration() (time.Duration, error) {
return ToDuration(string(*d))
}
func (d *AlertRulePromDurationWMillis) ToDuration() (time.Duration, error) {
return ToDuration(string(*d))
}
func (d *AlertRulePromDuration) Clamp() error {
clampedDuration, err := ClampDuration(string(*d))
if err != nil {
return err
}
*d = AlertRulePromDuration(clampedDuration)
return nil
}
func (d *AlertRulePromDurationWMillis) Clamp() error {
clampedDuration, err := ClampDuration(string(*d))
if err != nil {
return err
}
*d = AlertRulePromDurationWMillis(clampedDuration)
return nil
}
func (spec *AlertRuleSpec) ClampDurations() error {
// clamp all duration fields
if err := spec.Trigger.Interval.Clamp(); err != nil {
return err
}
if spec.For != nil {
clamped, err := ClampDuration(*spec.For)
if err != nil {
return err
}
spec.For = &clamped
}
if spec.KeepFiringFor != nil {
clamped, err := ClampDuration(*spec.KeepFiringFor)
if err != nil {
return err
}
spec.KeepFiringFor = &clamped
}
if spec.NotificationSettings != nil {
if spec.NotificationSettings.GroupWait != nil {
if err := spec.NotificationSettings.GroupWait.Clamp(); err != nil {
return err
}
}
if spec.NotificationSettings.GroupInterval != nil {
if err := spec.NotificationSettings.GroupInterval.Clamp(); err != nil {
return err
}
}
if spec.NotificationSettings.RepeatInterval != nil {
if err := spec.NotificationSettings.RepeatInterval.Clamp(); err != nil {
return err
}
}
}
for k, expr := range spec.Expressions {
if expr.RelativeTimeRange != nil {
if err := expr.RelativeTimeRange.From.Clamp(); err != nil {
return err
}
if err := expr.RelativeTimeRange.To.Clamp(); err != nil {
return err
}
spec.Expressions[k] = expr
}
}
return nil
}

View File

@@ -1,10 +1,22 @@
package v0alpha1
import (
"fmt"
"time"
prom_model "github.com/prometheus/common/model"
)
const (
InternalPrefix = "grafana.com/"
GroupLabelKey = InternalPrefix + "group"
GroupIndexLabelKey = GroupLabelKey + "-index"
ProvenanceStatusAnnotationKey = InternalPrefix + "provenance"
// Copy of the max title length used in legacy validation path
AlertRuleMaxTitleLength = 190
// Annotation key used to store the folder UID on resources
FolderAnnotationKey = "grafana.app/folder"
FolderLabelKey = FolderAnnotationKey
)
const (
@@ -15,3 +27,20 @@ const (
var (
AcceptedProvenanceStatuses = []string{ProvenanceStatusNone, ProvenanceStatusAPI}
)
func ToDuration(s string) (time.Duration, error) {
promDuration, err := prom_model.ParseDuration(s)
if err != nil {
return 0, fmt.Errorf("invalid duration format: %w", err)
}
return time.Duration(promDuration), nil
}
// Convert the string duration to the longest valid Prometheus duration format (e.g., "60s" -> "1m")
func ClampDuration(s string) (string, error) {
promDuration, err := prom_model.ParseDuration(s)
if err != nil {
return "", fmt.Errorf("invalid duration format: %w", err)
}
return promDuration.String(), nil
}

View File

@@ -3,6 +3,7 @@ package v0alpha1
import (
"fmt"
"slices"
"time"
)
func (o *RecordingRule) GetProvenanceStatus() string {
@@ -27,4 +28,47 @@ func (o *RecordingRule) SetProvenanceStatus(status string) (err error) {
return
}
// TODO: add duration clamping for the field types RecordingRulePromDurationWMillis and RecordingRulePromDuration
func (d *RecordingRulePromDuration) ToDuration() (time.Duration, error) {
return ToDuration(string(*d))
}
func (d *RecordingRulePromDurationWMillis) ToDuration() (time.Duration, error) {
return ToDuration(string(*d))
}
func (d *RecordingRulePromDuration) Clamp() error {
clampedDuration, err := ClampDuration(string(*d))
if err != nil {
return err
}
*d = RecordingRulePromDuration(clampedDuration)
return nil
}
func (d *RecordingRulePromDurationWMillis) Clamp() error {
clampedDuration, err := ClampDuration(string(*d))
if err != nil {
return err
}
*d = RecordingRulePromDurationWMillis(clampedDuration)
return nil
}
func (spec *RecordingRuleSpec) ClampDurations() error {
// clamp all duration fields
if err := spec.Trigger.Interval.Clamp(); err != nil {
return err
}
for k, expr := range spec.Expressions {
if expr.RelativeTimeRange != nil {
if err := expr.RelativeTimeRange.From.Clamp(); err != nil {
return err
}
if err := expr.RelativeTimeRange.To.Clamp(); err != nil {
return err
}
spec.Expressions[k] = expr
}
}
return nil
}

View File

@@ -42,7 +42,21 @@ var appManifestData = app.ManifestData{
Plural: "AlertRules",
Scope: "Namespaced",
Conversion: false,
Schema: &versionSchemaAlertRulev0alpha1,
Admission: &app.AdmissionCapabilities{
Validation: &app.ValidationCapability{
Operations: []app.AdmissionOperation{
app.AdmissionOperationCreate,
app.AdmissionOperationUpdate,
},
},
Mutation: &app.MutationCapability{
Operations: []app.AdmissionOperation{
app.AdmissionOperationCreate,
app.AdmissionOperationUpdate,
},
},
},
Schema: &versionSchemaAlertRulev0alpha1,
SelectableFields: []string{
"spec.title",
"spec.paused",
@@ -57,7 +71,21 @@ var appManifestData = app.ManifestData{
Plural: "RecordingRules",
Scope: "Namespaced",
Conversion: false,
Schema: &versionSchemaRecordingRulev0alpha1,
Admission: &app.AdmissionCapabilities{
Validation: &app.ValidationCapability{
Operations: []app.AdmissionOperation{
app.AdmissionOperationCreate,
app.AdmissionOperationUpdate,
},
},
Mutation: &app.MutationCapability{
Operations: []app.AdmissionOperation{
app.AdmissionOperationCreate,
app.AdmissionOperationUpdate,
},
},
},
Schema: &versionSchemaRecordingRulev0alpha1,
SelectableFields: []string{
"spec.title",
"spec.paused",

View File

@@ -0,0 +1,45 @@
package alertrule
import (
"context"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/simple"
v1 "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/config"
)
func NewMutator(cfg config.RuntimeConfig) *simple.Mutator {
return &simple.Mutator{
MutateFunc: func(ctx context.Context, req *app.AdmissionRequest) (*app.MutatingResponse, error) {
// Mutate folder label to match folder UID from annotation
r, ok := req.Object.(*v1.AlertRule)
if !ok || r == nil {
// Nothing to do or wrong type; no mutation
return nil, nil
}
// Read folder UID from annotation
folderUID := ""
if r.Annotations != nil {
folderUID = r.Annotations[v1.FolderAnnotationKey]
}
// Ensure labels map exists and set the folder label if folderUID is present
if folderUID != "" {
if r.Labels == nil {
r.Labels = make(map[string]string)
}
// Maintain folder metadata label for downstream systems (alertmanager grouping etc.)
r.Labels[v1.FolderLabelKey] = folderUID
}
// clamp all duration fields
if err := r.Spec.ClampDurations(); err != nil {
return nil, err
}
return &app.MutatingResponse{UpdatedObject: r}, nil
},
}
}

View File

@@ -0,0 +1,123 @@
package alertrule
import (
"context"
"fmt"
"slices"
"strconv"
"time"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana-app-sdk/simple"
model "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/config"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/util"
prom_model "github.com/prometheus/common/model"
)
func NewValidator(cfg config.RuntimeConfig) *simple.Validator {
return &simple.Validator{
ValidateFunc: func(ctx context.Context, req *app.AdmissionRequest) error {
// Cast to specific type
r, ok := req.Object.(*model.AlertRule)
if !ok {
return fmt.Errorf("object is not of type *v0alpha1.AlertRule")
}
// 1) Validate provenance status annotation
sourceProv := r.GetProvenanceStatus()
if !slices.Contains(model.AcceptedProvenanceStatuses, sourceProv) {
return fmt.Errorf("invalid provenance status: %s", sourceProv)
}
// 2) Validate group labels rules
group := r.Labels[model.GroupLabelKey]
groupIndexStr := r.Labels[model.GroupIndexLabelKey]
if req.Action == resource.AdmissionActionCreate {
if group != "" || groupIndexStr != "" {
return fmt.Errorf("cannot set group when creating alert rule")
}
}
if group != "" { // if group is set, group-index must be set and numeric
if groupIndexStr == "" {
return fmt.Errorf("%s must be set when %s is set", model.GroupIndexLabelKey, model.GroupLabelKey)
}
if _, err := strconv.Atoi(groupIndexStr); err != nil {
return fmt.Errorf("invalid %s: %w", model.GroupIndexLabelKey, err)
}
}
// 3) Validate folder is set and exists
// Read folder UID directly from annotations
folderUID := ""
if r.Annotations != nil {
folderUID = r.Annotations[model.FolderAnnotationKey]
}
if folderUID == "" {
return fmt.Errorf("folder is required")
}
if cfg.FolderValidator != nil {
ok, verr := cfg.FolderValidator(ctx, folderUID)
if verr != nil {
return fmt.Errorf("failed to validate folder: %w", verr)
}
if !ok {
return fmt.Errorf("folder does not exist: %s", folderUID)
}
}
// 4) Validate notification settings receiver if provided
if r.Spec.NotificationSettings != nil && r.Spec.NotificationSettings.Receiver != "" && cfg.NotificationSettingsValidator != nil {
ok, nerr := cfg.NotificationSettingsValidator(ctx, r.Spec.NotificationSettings.Receiver)
if nerr != nil {
return fmt.Errorf("failed to validate notification settings: %w", nerr)
}
if !ok {
return fmt.Errorf("invalid notification receiver: %s", r.Spec.NotificationSettings.Receiver)
}
}
// 5) Enforce max title length
if len(r.Spec.Title) > model.AlertRuleMaxTitleLength {
return fmt.Errorf("alert rule title is too long. Max length is %d", model.AlertRuleMaxTitleLength)
}
// 6) Validate evaluation interval against base interval
if err := util.ValidateInterval(cfg.BaseEvaluationInterval, &r.Spec.Trigger.Interval); err != nil {
return err
}
// 7) Disallow reserved/spec system label keys
if r.Spec.Labels != nil {
for key := range r.Spec.Labels {
if _, bad := cfg.ReservedLabelKeys[key]; bad {
return fmt.Errorf("label key is reserved and cannot be specified: %s", key)
}
}
}
// 8) For and KeepFiringFor must be >= 0 if set
if r.Spec.For != nil {
d, err := prom_model.ParseDuration(*r.Spec.For)
if err != nil {
return fmt.Errorf("invalid 'for' duration: %w", err)
}
if time.Duration(d) < 0 {
return fmt.Errorf("'for' cannot be less than 0")
}
}
if r.Spec.KeepFiringFor != nil {
d, err := prom_model.ParseDuration(*r.Spec.KeepFiringFor)
if err != nil {
return fmt.Errorf("invalid 'keepFiringFor' duration: %w", err)
}
if time.Duration(d) < 0 {
return fmt.Errorf("'keepFiringFor' cannot be less than 0")
}
}
return nil
},
}
}

View File

@@ -6,16 +6,29 @@ 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"
"github.com/grafana/grafana/apps/alerting/rules/pkg/apis"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/alertrule"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/config"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/recordingrule"
)
func New(cfg app.Config) (app.App, error) {
managedKinds := make([]simple.AppManagedKind, 0)
runtimeCfg, ok := cfg.SpecificConfig.(config.RuntimeConfig)
if !ok {
return nil, config.ErrInvalidRuntimeConfig
}
for _, kinds := range apis.GetKinds() {
for _, kind := range kinds {
managedKinds = append(managedKinds, simple.AppManagedKind{Kind: kind})
managedKind := simple.AppManagedKind{
Kind: kind,
Validator: buildKindValidator(kind, runtimeCfg),
Mutator: buildKindMutator(kind, runtimeCfg),
}
managedKinds = append(managedKinds, managedKind)
}
}
@@ -44,3 +57,23 @@ func New(cfg app.Config) (app.App, error) {
return a, nil
}
func buildKindValidator(kind resource.Kind, cfg config.RuntimeConfig) *simple.Validator {
switch kind.Kind() {
case "AlertRule":
return alertrule.NewValidator(cfg)
case "RecordingRule":
return recordingrule.NewValidator(cfg)
}
return nil
}
func buildKindMutator(kind resource.Kind, cfg config.RuntimeConfig) *simple.Mutator {
switch kind.Kind() {
case "AlertRule":
return alertrule.NewMutator(cfg)
case "RecordingRule":
return recordingrule.NewMutator(cfg)
}
return nil
}

View File

@@ -0,0 +1,175 @@
package app_test
import (
"context"
"testing"
"time"
appsdk "github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/resource"
v1 "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/alertrule"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/config"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/recordingrule"
)
func makeDefaultRuntimeConfig() config.RuntimeConfig {
return config.RuntimeConfig{
FolderValidator: func(ctx context.Context, folderUID string) (bool, error) { return folderUID == "f1", nil },
BaseEvaluationInterval: 60 * time.Second, // seconds
ReservedLabelKeys: map[string]struct{}{"__reserved__": {}, "grafana_folder": {}},
NotificationSettingsValidator: func(ctx context.Context, receiver string) (bool, error) { return receiver == "notif-ok", nil },
}
}
func TestAlertRuleValidation_Success(t *testing.T) {
r := &v1.AlertRule{}
r.SetGroupVersionKind(v1.AlertRuleKind().GroupVersionKind())
r.Name = "uid-1"
r.Namespace = "ns1"
r.Annotations = map[string]string{v1.FolderAnnotationKey: "f1"}
r.Labels = map[string]string{}
r.Spec = v1.AlertRuleSpec{
Title: "ok",
Trigger: v1.AlertRuleIntervalTrigger{Interval: v1.AlertRulePromDuration("60s")},
Expressions: v1.AlertRuleExpressionMap{"A": v1.AlertRuleExpression{Model: map[string]any{"expr": "1"}, Source: boolPtr(true)}},
NoDataState: v1.DefaultNoDataState,
ExecErrState: v1.DefaultExecErrState,
NotificationSettings: &v1.AlertRuleV0alpha1SpecNotificationSettings{Receiver: "notif-ok"},
}
req := &appsdk.AdmissionRequest{Action: resource.AdmissionActionCreate, Object: r}
validator := alertrule.NewValidator(makeDefaultRuntimeConfig())
if err := validator.Validate(context.Background(), req); err != nil {
t.Fatalf("expected success, got error: %v", err)
}
}
func TestAlertRuleValidation_Errors(t *testing.T) {
mk := func(mut func(r *v1.AlertRule)) error {
r := baseAlertRule()
mut(r)
return alertrule.NewValidator(makeDefaultRuntimeConfig()).Validate(context.Background(), &appsdk.AdmissionRequest{Action: resource.AdmissionActionCreate, Object: r})
}
if err := mk(func(r *v1.AlertRule) { r.Annotations = nil }); err == nil {
t.Errorf("want folder required error")
}
if err := mk(func(r *v1.AlertRule) { r.Annotations[v1.FolderAnnotationKey] = "bad" }); err == nil {
t.Errorf("want folder not exist error")
}
if err := mk(func(r *v1.AlertRule) { r.Spec.Trigger.Interval = v1.AlertRulePromDuration("30s") }); err == nil {
t.Errorf("want base interval multiple error")
}
if err := mk(func(r *v1.AlertRule) {
r.Spec.NotificationSettings = &v1.AlertRuleV0alpha1SpecNotificationSettings{Receiver: "bad"}
}); err == nil {
t.Errorf("want invalid receiver error")
}
if err := mk(func(r *v1.AlertRule) { r.Labels[v1.GroupLabelKey] = "grp" }); err == nil {
t.Errorf("want group set on create error")
}
if err := mk(func(r *v1.AlertRule) { r.Spec.For = strPtr("-10s") }); err == nil {
t.Errorf("want for>=0 error")
}
if err := mk(func(r *v1.AlertRule) {
if r.Spec.Labels == nil {
r.Spec.Labels = map[string]v1.AlertRuleTemplateString{}
}
r.Spec.Labels["__reserved__"] = v1.AlertRuleTemplateString("x")
}); err == nil {
t.Errorf("want reserved label key error")
}
}
func baseAlertRule() *v1.AlertRule {
r := &v1.AlertRule{}
r.SetGroupVersionKind(v1.AlertRuleKind().GroupVersionKind())
r.Name = "uid-1"
r.Namespace = "ns1"
r.Annotations = map[string]string{v1.FolderAnnotationKey: "f1"}
r.Labels = map[string]string{}
r.Spec = v1.AlertRuleSpec{
Title: "ok",
Trigger: v1.AlertRuleIntervalTrigger{Interval: v1.AlertRulePromDuration("60s")},
Expressions: v1.AlertRuleExpressionMap{"A": v1.AlertRuleExpression{Model: map[string]any{"expr": "1"}, Source: boolPtr(true)}},
NoDataState: v1.DefaultNoDataState,
ExecErrState: v1.DefaultExecErrState,
}
return r
}
func TestRecordingRuleValidation_Success(t *testing.T) {
r := &v1.RecordingRule{}
r.SetGroupVersionKind(v1.RecordingRuleKind().GroupVersionKind())
r.Name = "uid-2"
r.Namespace = "ns1"
r.Annotations = map[string]string{v1.FolderAnnotationKey: "f1"}
r.Labels = map[string]string{}
r.Spec = v1.RecordingRuleSpec{
Title: "ok",
Trigger: v1.RecordingRuleIntervalTrigger{Interval: v1.RecordingRulePromDuration("60s")},
Expressions: v1.RecordingRuleExpressionMap{"A": v1.RecordingRuleExpression{Model: map[string]any{"expr": "1"}, Source: boolPtr(true)}},
Metric: "test_metric",
TargetDatasourceUID: "ds1",
}
req := &appsdk.AdmissionRequest{Action: resource.AdmissionActionCreate, Object: r}
validator := recordingrule.NewValidator(makeDefaultRuntimeConfig())
if err := validator.Validate(context.Background(), req); err != nil {
t.Fatalf("expected success, got error: %v", err)
}
}
func TestRecordingRuleValidation_Errors(t *testing.T) {
mk := func(mut func(r *v1.RecordingRule)) error {
r := baseRecordingRule()
mut(r)
return recordingrule.NewValidator(makeDefaultRuntimeConfig()).Validate(context.Background(), &appsdk.AdmissionRequest{Action: resource.AdmissionActionCreate, Object: r})
}
if err := mk(func(r *v1.RecordingRule) { r.Annotations = nil }); err == nil {
t.Errorf("want folder required error")
}
if err := mk(func(r *v1.RecordingRule) { r.Annotations[v1.FolderAnnotationKey] = "bad" }); err == nil {
t.Errorf("want folder not exist error")
}
if err := mk(func(r *v1.RecordingRule) { r.Spec.Trigger.Interval = v1.RecordingRulePromDuration("30s") }); err == nil {
t.Errorf("want base interval multiple error")
}
if err := mk(func(r *v1.RecordingRule) { r.Labels[v1.GroupLabelKey] = "grp" }); err == nil {
t.Errorf("want group set on create error")
}
if err := mk(func(r *v1.RecordingRule) { r.Spec.Metric = "" }); err == nil {
t.Errorf("want metric required error")
}
if err := mk(func(r *v1.RecordingRule) {
if r.Spec.Labels == nil {
r.Spec.Labels = map[string]v1.RecordingRuleTemplateString{}
}
r.Spec.Labels["__reserved__"] = v1.RecordingRuleTemplateString("x")
}); err == nil {
t.Errorf("want reserved label key error")
}
}
func baseRecordingRule() *v1.RecordingRule {
r := &v1.RecordingRule{}
r.SetGroupVersionKind(v1.RecordingRuleKind().GroupVersionKind())
r.Name = "uid-1"
r.Namespace = "ns1"
r.Annotations = map[string]string{v1.FolderAnnotationKey: "f1"}
r.Labels = map[string]string{}
r.Spec = v1.RecordingRuleSpec{
Title: "ok",
Trigger: v1.RecordingRuleIntervalTrigger{Interval: v1.RecordingRulePromDuration("60s")},
Expressions: v1.RecordingRuleExpressionMap{"A": v1.RecordingRuleExpression{Model: map[string]any{"expr": "1"}, Source: boolPtr(true)}},
Metric: "test_metric",
TargetDatasourceUID: "ds1",
}
return r
}
func boolPtr(b bool) *bool { return &b }
func strPtr(s string) *string { return &s }

View File

@@ -0,0 +1,22 @@
package config
import (
"context"
"errors"
"time"
)
var (
ErrInvalidRuntimeConfig = errors.New("invalid runtime config provided to alerting/rules app")
)
// RuntimeConfig holds configuration values needed at runtime by the alerting/rules app from the running Grafana instance.
type RuntimeConfig struct {
// function to check folder existence given its uid
FolderValidator func(ctx context.Context, folderUID string) (bool, error)
// base evaluation interval
BaseEvaluationInterval time.Duration
// set of strings which are illegal for label keys on rules
ReservedLabelKeys map[string]struct{}
NotificationSettingsValidator func(ctx context.Context, receiver string) (bool, error)
}

View File

@@ -0,0 +1,37 @@
package recordingrule
import (
"context"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/simple"
v1 "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/config"
)
func NewMutator(cfg config.RuntimeConfig) *simple.Mutator {
return &simple.Mutator{
MutateFunc: func(ctx context.Context, req *app.AdmissionRequest) (*app.MutatingResponse, error) {
r, ok := req.Object.(*v1.RecordingRule)
if !ok || r == nil {
return nil, nil
}
folderUID := ""
if r.Annotations != nil {
folderUID = r.Annotations[v1.FolderAnnotationKey]
}
if folderUID != "" {
if r.Labels == nil {
r.Labels = make(map[string]string)
}
r.Labels[v1.FolderLabelKey] = folderUID
}
if err := r.Spec.ClampDurations(); err != nil {
return nil, err
}
return &app.MutatingResponse{UpdatedObject: r}, nil
},
}
}

View File

@@ -0,0 +1,95 @@
package recordingrule
import (
"context"
"fmt"
"slices"
"strconv"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana-app-sdk/simple"
model "github.com/grafana/grafana/apps/alerting/rules/pkg/apis/alerting/v0alpha1"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/config"
"github.com/grafana/grafana/apps/alerting/rules/pkg/app/util"
prom_model "github.com/prometheus/common/model"
)
func NewValidator(cfg config.RuntimeConfig) *simple.Validator {
return &simple.Validator{
ValidateFunc: func(ctx context.Context, req *app.AdmissionRequest) error {
// Cast to specific type
r, ok := req.Object.(*model.RecordingRule)
if !ok {
return fmt.Errorf("object is not of type *v0alpha1.RecordingRule")
}
sourceProv := r.GetProvenanceStatus()
if !slices.Contains(model.AcceptedProvenanceStatuses, sourceProv) {
return fmt.Errorf("invalid provenance status: %s", sourceProv)
}
group := r.Labels[model.GroupLabelKey]
groupIndexStr := r.Labels[model.GroupIndexLabelKey]
if req.Action == resource.AdmissionActionCreate {
if group != "" || groupIndexStr != "" {
return fmt.Errorf("cannot set group when creating recording rule")
}
}
if group != "" {
if groupIndexStr == "" {
return fmt.Errorf("%s must be set when %s is set", model.GroupIndexLabelKey, model.GroupLabelKey)
}
if _, err := strconv.Atoi(groupIndexStr); err != nil {
return fmt.Errorf("invalid %s: %w", model.GroupIndexLabelKey, err)
}
}
folderUID := ""
if r.Annotations != nil {
folderUID = r.Annotations[model.FolderAnnotationKey]
}
if folderUID == "" {
return fmt.Errorf("folder is required")
}
if cfg.FolderValidator != nil {
ok, verr := cfg.FolderValidator(ctx, folderUID)
if verr != nil {
return fmt.Errorf("failed to validate folder: %w", verr)
}
if !ok {
return fmt.Errorf("folder does not exist: %s", folderUID)
}
}
if len(r.Spec.Title) > model.AlertRuleMaxTitleLength {
return fmt.Errorf("recording rule title is too long. Max length is %d", model.AlertRuleMaxTitleLength)
}
if err := util.ValidateInterval(cfg.BaseEvaluationInterval, &r.Spec.Trigger.Interval); err != nil {
return err
}
if r.Spec.Labels != nil {
for key := range r.Spec.Labels {
if _, bad := cfg.ReservedLabelKeys[key]; bad {
return fmt.Errorf("label key is reserved and cannot be specified: %s", key)
}
}
}
if r.Spec.Metric == "" {
return fmt.Errorf("metric must be specified")
}
metric := prom_model.LabelValue(r.Spec.Metric)
if !metric.IsValid() {
return fmt.Errorf("metric contains invalid characters")
}
if !prom_model.IsValidMetricName(metric) { // nolint:staticcheck
return fmt.Errorf("invalid metric name")
}
return nil
},
}
}

View File

@@ -0,0 +1,27 @@
package util
import (
"fmt"
"time"
)
type DurationLike interface {
ToDuration() (time.Duration, error)
}
func ValidateInterval(baseInterval time.Duration, d DurationLike) error {
interval, err := d.ToDuration()
if err != nil {
return fmt.Errorf("invalid trigger interval: %w", err)
}
// Ensure interval is positive and an integer multiple of BaseEvaluationInterval (if provided)
if interval <= 0 {
return fmt.Errorf("trigger interval must be greater than 0")
}
if baseInterval > 0 {
if (interval % baseInterval) != 0 {
return fmt.Errorf("trigger interval must be a multiple of base evaluation interval (%s)", baseInterval.String())
}
}
return nil
}

9
apps/annotation/Makefile Normal file
View File

@@ -0,0 +1,9 @@
include ../sdk.mk
.PHONY: generate # Run Grafana App SDK code generation
generate: install-app-sdk update-app-sdk
@$(APP_SDK_BIN) generate \
--source=./kinds/ \
--gogenpath=./pkg/apis \
--grouping=group \
--defencoding=none

93
apps/annotation/go.mod Normal file
View File

@@ -0,0 +1,93 @@
module github.com/grafana/grafana/apps/annotation
go 1.24.0
require (
github.com/grafana/grafana-app-sdk v0.48.1
github.com/grafana/grafana-app-sdk/logging v0.48.1
k8s.io/apimachinery v0.34.1
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912
)
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 // indirect
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.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
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.22.1 // indirect
github.com/go-openapi/jsonreference v0.21.2 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/swag/jsonname v0.25.1 // indirect
github.com/go-test/deep v1.1.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/gnostic-models v0.7.0 // indirect
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/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect
github.com/onsi/ginkgo/v2 v2.22.2 // indirect
github.com/onsi/gomega v1.36.2 // indirect
github.com/perimeterx/marshmallow v1.1.5 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.1 // indirect
github.com/prometheus/procfs v0.16.1 // indirect
github.com/puzpuzpuz/xsync/v2 v2.5.1 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/woodsbury/decimal128 v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
go.opentelemetry.io/otel/metric v1.38.0 // indirect
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
go.opentelemetry.io/otel/trace v1.38.0 // indirect
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/net v0.46.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.37.0 // indirect
golang.org/x/term v0.36.0 // indirect
golang.org/x/text v0.30.0 // indirect
golang.org/x/time v0.14.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect
google.golang.org/grpc v1.76.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/api v0.34.1 // indirect
k8s.io/apiextensions-apiserver v0.34.1 // indirect
k8s.io/client-go v0.34.1 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
sigs.k8s.io/yaml v1.6.0 // indirect
)

240
apps/annotation/go.sum Normal file
View File

@@ -0,0 +1,240 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874 h1:N7oVaKyGp8bttX0bfZGmcGkjz7DLQXhAn3DNd3T0ous=
github.com/bradfitz/gomemcache v0.0.0-20230905024940-24af94b03874/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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.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=
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=
github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk=
github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM=
github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU=
github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU=
github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
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/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8=
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.48.1 h1:bKJadWH18WCpJ+Zk8AezRFXCcZgGredRv+fRS+8zkek=
github.com/grafana/grafana-app-sdk v0.48.1/go.mod h1:5LljCz+wvmGfkQ8ZKTOfserhtXNEF0cSFthoWShvN6c=
github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng=
github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY=
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037/go.mod h1:2bpvgLBZEtENV5scfDFEtB/5+1M4hkQhDQrccEJ/qGw=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 h1:bQx3WeLcUWy+RletIKwUIt4x3t8n2SxavmoclizMb8c=
github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o=
github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU=
github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk=
github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8=
github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY=
github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s=
github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.1 h1:OTSON1P4DNxzTg4hmKCc37o4ZAZDv0cfXLkOt0oEowI=
github.com/prometheus/common v0.67.1/go.mod h1:RpmT9v35q2Y+lsieQsdOh5sXZ6ajUGC8NjZAmr8vb0Q=
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
github.com/puzpuzpuz/xsync/v2 v2.5.1 h1:mVGYAvzDSu52+zaGyNjC+24Xw2bQi3kTr4QJ6N9pIIU=
github.com/puzpuzpuz/xsync/v2 v2.5.1/go.mod h1:gD2H2krq/w52MfPLE+Uy64TzJDVY7lP2znR9qmR35kU=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0=
github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210=
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY=
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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=
gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0=
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090 h1:d8Nakh1G+ur7+P3GcMjpRDEkoLUcLW2iU92XVqR+XMQ=
google.golang.org/genproto/googleapis/api v0.0.0-20250908214217-97024824d090/go.mod h1:U8EXRNSd8sUYyDfs/It7KVWodQr+Hf9xtxyxWudSwEw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 h1:CirRxTOwnRWVLKzDNrs0CXAaVozJoR4G9xvdRecrdpk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A=
google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
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=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM=
k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk=
k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI=
k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc=
k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4=
k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY=
k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE=
k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@@ -0,0 +1,17 @@
package kinds
annotationv0alpha1: {
kind: "Annotation"
pluralName: "Annotations"
schema: {
spec: {
text: string
time: int64
timeEnd?: int64
dashboardUID?: string
panelID?: int64
tags?: [...string]
}
}
}

View File

@@ -0,0 +1,2 @@
module: "github.com/grafana/grafana/apps/annotation/kinds"
language: version: "v0.8.2"

View File

@@ -0,0 +1,21 @@
package kinds
manifest: {
appName: "annotation"
groupOverride: "annotation.grafana.app"
versions: {
"v0alpha1": v0alpha1
}
}
v0alpha1: {
kinds: [annotationv0alpha1]
codegen: {
ts: {
enabled: true
}
go: {
enabled: true
}
}
}

View File

@@ -0,0 +1,99 @@
package v0alpha1
import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type AnnotationClient struct {
client *resource.TypedClient[*Annotation, *AnnotationList]
}
func NewAnnotationClient(client resource.Client) *AnnotationClient {
return &AnnotationClient{
client: resource.NewTypedClient[*Annotation, *AnnotationList](client, AnnotationKind()),
}
}
func NewAnnotationClientFromGenerator(generator resource.ClientGenerator) (*AnnotationClient, error) {
c, err := generator.ClientFor(AnnotationKind())
if err != nil {
return nil, err
}
return NewAnnotationClient(c), nil
}
func (c *AnnotationClient) Get(ctx context.Context, identifier resource.Identifier) (*Annotation, error) {
return c.client.Get(ctx, identifier)
}
func (c *AnnotationClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*AnnotationList, error) {
return c.client.List(ctx, namespace, opts)
}
func (c *AnnotationClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*AnnotationList, 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 *AnnotationClient) Create(ctx context.Context, obj *Annotation, opts resource.CreateOptions) (*Annotation, error) {
// Make sure apiVersion and kind are set
obj.APIVersion = GroupVersion.Identifier()
obj.Kind = AnnotationKind().Kind()
return c.client.Create(ctx, obj, opts)
}
func (c *AnnotationClient) Update(ctx context.Context, obj *Annotation, opts resource.UpdateOptions) (*Annotation, error) {
return c.client.Update(ctx, obj, opts)
}
func (c *AnnotationClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*Annotation, error) {
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *AnnotationClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus AnnotationStatus, opts resource.UpdateOptions) (*Annotation, error) {
return c.client.Update(ctx, &Annotation{
TypeMeta: metav1.TypeMeta{
Kind: AnnotationKind().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 *AnnotationClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@@ -0,0 +1,28 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"encoding/json"
"io"
"github.com/grafana/grafana-app-sdk/resource"
)
// AnnotationJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
type AnnotationJSONCodec struct{}
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
func (*AnnotationJSONCodec) Read(reader io.Reader, into resource.Object) error {
return json.NewDecoder(reader).Decode(into)
}
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
func (*AnnotationJSONCodec) Write(writer io.Writer, from resource.Object) error {
return json.NewEncoder(writer).Encode(from)
}
// Interface compliance checks
var _ resource.Codec = &AnnotationJSONCodec{}

View File

@@ -0,0 +1,31 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
import (
time "time"
)
// metadata contains embedded CommonMetadata and can be extended with custom string fields
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
// without external reference as using the CommonMetadata reference breaks thema codegen.
type AnnotationMetadata struct {
UpdateTimestamp time.Time `json:"updateTimestamp"`
CreatedBy string `json:"createdBy"`
Uid string `json:"uid"`
CreationTimestamp time.Time `json:"creationTimestamp"`
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
Finalizers []string `json:"finalizers"`
ResourceVersion string `json:"resourceVersion"`
Generation int64 `json:"generation"`
UpdatedBy string `json:"updatedBy"`
Labels map[string]string `json:"labels"`
}
// NewAnnotationMetadata creates a new AnnotationMetadata object.
func NewAnnotationMetadata() *AnnotationMetadata {
return &AnnotationMetadata{
Finalizers: []string{},
Labels: map[string]string{},
}
}

View File

@@ -0,0 +1,319 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"fmt"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"time"
)
// +k8s:openapi-gen=true
type Annotation struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
// Spec is the spec of the Annotation
Spec AnnotationSpec `json:"spec" yaml:"spec"`
Status AnnotationStatus `json:"status" yaml:"status"`
}
func (o *Annotation) GetSpec() any {
return o.Spec
}
func (o *Annotation) SetSpec(spec any) error {
cast, ok := spec.(AnnotationSpec)
if !ok {
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
}
o.Spec = cast
return nil
}
func (o *Annotation) GetSubresources() map[string]any {
return map[string]any{
"status": o.Status,
}
}
func (o *Annotation) GetSubresource(name string) (any, bool) {
switch name {
case "status":
return o.Status, true
default:
return nil, false
}
}
func (o *Annotation) SetSubresource(name string, value any) error {
switch name {
case "status":
cast, ok := value.(AnnotationStatus)
if !ok {
return fmt.Errorf("cannot set status type %#v, not of type AnnotationStatus", value)
}
o.Status = cast
return nil
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
}
func (o *Annotation) GetStaticMetadata() resource.StaticMetadata {
gvk := o.GroupVersionKind()
return resource.StaticMetadata{
Name: o.ObjectMeta.Name,
Namespace: o.ObjectMeta.Namespace,
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
}
}
func (o *Annotation) SetStaticMetadata(metadata resource.StaticMetadata) {
o.Name = metadata.Name
o.Namespace = metadata.Namespace
o.SetGroupVersionKind(schema.GroupVersionKind{
Group: metadata.Group,
Version: metadata.Version,
Kind: metadata.Kind,
})
}
func (o *Annotation) GetCommonMetadata() resource.CommonMetadata {
dt := o.DeletionTimestamp
var deletionTimestamp *time.Time
if dt != nil {
deletionTimestamp = &dt.Time
}
// Legacy ExtraFields support
extraFields := make(map[string]any)
if o.Annotations != nil {
extraFields["annotations"] = o.Annotations
}
if o.ManagedFields != nil {
extraFields["managedFields"] = o.ManagedFields
}
if o.OwnerReferences != nil {
extraFields["ownerReferences"] = o.OwnerReferences
}
return resource.CommonMetadata{
UID: string(o.UID),
ResourceVersion: o.ResourceVersion,
Generation: o.Generation,
Labels: o.Labels,
CreationTimestamp: o.CreationTimestamp.Time,
DeletionTimestamp: deletionTimestamp,
Finalizers: o.Finalizers,
UpdateTimestamp: o.GetUpdateTimestamp(),
CreatedBy: o.GetCreatedBy(),
UpdatedBy: o.GetUpdatedBy(),
ExtraFields: extraFields,
}
}
func (o *Annotation) SetCommonMetadata(metadata resource.CommonMetadata) {
o.UID = types.UID(metadata.UID)
o.ResourceVersion = metadata.ResourceVersion
o.Generation = metadata.Generation
o.Labels = metadata.Labels
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
if metadata.DeletionTimestamp != nil {
dt := metav1.NewTime(*metadata.DeletionTimestamp)
o.DeletionTimestamp = &dt
} else {
o.DeletionTimestamp = nil
}
o.Finalizers = metadata.Finalizers
if o.Annotations == nil {
o.Annotations = make(map[string]string)
}
if !metadata.UpdateTimestamp.IsZero() {
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
}
if metadata.CreatedBy != "" {
o.SetCreatedBy(metadata.CreatedBy)
}
if metadata.UpdatedBy != "" {
o.SetUpdatedBy(metadata.UpdatedBy)
}
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
if metadata.ExtraFields != nil {
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
if cast, ok := annotations.(map[string]string); ok {
o.Annotations = cast
}
}
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
o.ManagedFields = cast
}
}
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
o.OwnerReferences = cast
}
}
}
}
func (o *Annotation) GetCreatedBy() string {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
}
func (o *Annotation) SetCreatedBy(createdBy string) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
}
func (o *Annotation) GetUpdateTimestamp() time.Time {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
return parsed
}
func (o *Annotation) SetUpdateTimestamp(updateTimestamp time.Time) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
}
func (o *Annotation) GetUpdatedBy() string {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
}
func (o *Annotation) SetUpdatedBy(updatedBy string) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
}
func (o *Annotation) Copy() resource.Object {
return resource.CopyObject(o)
}
func (o *Annotation) DeepCopyObject() runtime.Object {
return o.Copy()
}
func (o *Annotation) DeepCopy() *Annotation {
cpy := &Annotation{}
o.DeepCopyInto(cpy)
return cpy
}
func (o *Annotation) DeepCopyInto(dst *Annotation) {
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
dst.TypeMeta.Kind = o.TypeMeta.Kind
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
o.Spec.DeepCopyInto(&dst.Spec)
o.Status.DeepCopyInto(&dst.Status)
}
// Interface compliance compile-time check
var _ resource.Object = &Annotation{}
// +k8s:openapi-gen=true
type AnnotationList struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ListMeta `json:"metadata" yaml:"metadata"`
Items []Annotation `json:"items" yaml:"items"`
}
func (o *AnnotationList) DeepCopyObject() runtime.Object {
return o.Copy()
}
func (o *AnnotationList) Copy() resource.ListObject {
cpy := &AnnotationList{
TypeMeta: o.TypeMeta,
Items: make([]Annotation, len(o.Items)),
}
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
for i := 0; i < len(o.Items); i++ {
if item, ok := o.Items[i].Copy().(*Annotation); ok {
cpy.Items[i] = *item
}
}
return cpy
}
func (o *AnnotationList) GetItems() []resource.Object {
items := make([]resource.Object, len(o.Items))
for i := 0; i < len(o.Items); i++ {
items[i] = &o.Items[i]
}
return items
}
func (o *AnnotationList) SetItems(items []resource.Object) {
o.Items = make([]Annotation, len(items))
for i := 0; i < len(items); i++ {
o.Items[i] = *items[i].(*Annotation)
}
}
func (o *AnnotationList) DeepCopy() *AnnotationList {
cpy := &AnnotationList{}
o.DeepCopyInto(cpy)
return cpy
}
func (o *AnnotationList) DeepCopyInto(dst *AnnotationList) {
resource.CopyObjectInto(dst, o)
}
// Interface compliance compile-time check
var _ resource.ListObject = &AnnotationList{}
// Copy methods for all subresource types
// DeepCopy creates a full deep copy of Spec
func (s *AnnotationSpec) DeepCopy() *AnnotationSpec {
cpy := &AnnotationSpec{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies Spec into another Spec object
func (s *AnnotationSpec) DeepCopyInto(dst *AnnotationSpec) {
resource.CopyObjectInto(dst, s)
}
// DeepCopy creates a full deep copy of AnnotationStatus
func (s *AnnotationStatus) DeepCopy() *AnnotationStatus {
cpy := &AnnotationStatus{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies AnnotationStatus into another AnnotationStatus object
func (s *AnnotationStatus) DeepCopyInto(dst *AnnotationStatus) {
resource.CopyObjectInto(dst, s)
}

View File

@@ -0,0 +1,34 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"github.com/grafana/grafana-app-sdk/resource"
)
// schema is unexported to prevent accidental overwrites
var (
schemaAnnotation = resource.NewSimpleSchema("annotation.grafana.app", "v0alpha1", &Annotation{}, &AnnotationList{}, resource.WithKind("Annotation"),
resource.WithPlural("annotations"), resource.WithScope(resource.NamespacedScope))
kindAnnotation = resource.Kind{
Schema: schemaAnnotation,
Codecs: map[resource.KindEncoding]resource.Codec{
resource.KindEncodingJSON: &AnnotationJSONCodec{},
},
}
)
// Kind returns a resource.Kind for this Schema with a JSON codec
func AnnotationKind() resource.Kind {
return kindAnnotation
}
// Schema returns a resource.SimpleSchema representation of Annotation
func AnnotationSchema() *resource.SimpleSchema {
return schemaAnnotation
}
// Interface compliance checks
var _ resource.Schema = kindAnnotation

View File

@@ -0,0 +1,18 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
// +k8s:openapi-gen=true
type AnnotationSpec struct {
Text string `json:"text"`
Time int64 `json:"time"`
TimeEnd *int64 `json:"timeEnd,omitempty"`
DashboardUID *string `json:"dashboardUID,omitempty"`
PanelID *int64 `json:"panelID,omitempty"`
Tags []string `json:"tags,omitempty"`
}
// NewAnnotationSpec creates a new AnnotationSpec object.
func NewAnnotationSpec() *AnnotationSpec {
return &AnnotationSpec{}
}

View File

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

View File

@@ -0,0 +1,18 @@
package v0alpha1
import "k8s.io/apimachinery/pkg/runtime/schema"
const (
// APIGroup is the API group used by all kinds in this package
APIGroup = "annotation.grafana.app"
// APIVersion is the API version used by all kinds in this package
APIVersion = "v0alpha1"
)
var (
// GroupVersion is a schema.GroupVersion consisting of the Group and Version constants for this package
GroupVersion = schema.GroupVersion{
Group: APIGroup,
Version: APIVersion,
}
)

View File

@@ -0,0 +1,124 @@
//
// This file is generated by grafana-app-sdk
// DO NOT EDIT
//
package apis
import (
"encoding/json"
"fmt"
"strings"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/resource"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kube-openapi/pkg/spec3"
"k8s.io/kube-openapi/pkg/validation/spec"
v0alpha1 "github.com/grafana/grafana/apps/annotation/pkg/apis/annotation/v0alpha1"
)
var (
rawSchemaAnnotationv0alpha1 = []byte(`{"Annotation":{"properties":{"spec":{"$ref":"#/components/schemas/spec"},"status":{"$ref":"#/components/schemas/status"}},"required":["spec"]},"OperatorState":{"additionalProperties":false,"properties":{"descriptiveState":{"description":"descriptiveState is an optional more descriptive state field which has no requirements on format","type":"string"},"details":{"additionalProperties":{"additionalProperties":{},"type":"object"},"description":"details contains any extra information that is operator-specific","type":"object"},"lastEvaluation":{"description":"lastEvaluation is the ResourceVersion last evaluated","type":"string"},"state":{"description":"state describes the state of the lastEvaluation.\nIt is limited to three possible states for machine evaluation.","enum":["success","in_progress","failed"],"type":"string"}},"required":["lastEvaluation","state"],"type":"object"},"spec":{"additionalProperties":false,"properties":{"dashboardUID":{"type":"string"},"panelID":{"type":"integer"},"tags":{"items":{"type":"string"},"type":"array"},"text":{"type":"string"},"time":{"type":"integer"},"timeEnd":{"type":"integer"}},"required":["text","time"],"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"}}`)
versionSchemaAnnotationv0alpha1 app.VersionSchema
_ = json.Unmarshal(rawSchemaAnnotationv0alpha1, &versionSchemaAnnotationv0alpha1)
)
var appManifestData = app.ManifestData{
AppName: "annotation",
Group: "annotation.grafana.app",
PreferredVersion: "v0alpha1",
Versions: []app.ManifestVersion{
{
Name: "v0alpha1",
Served: true,
Kinds: []app.ManifestVersionKind{
{
Kind: "Annotation",
Plural: "Annotations",
Scope: "Namespaced",
Conversion: false,
Schema: &versionSchemaAnnotationv0alpha1,
},
},
Routes: app.ManifestVersionRoutes{
Namespaced: map[string]spec3.PathProps{},
Cluster: map[string]spec3.PathProps{},
Schemas: map[string]spec.Schema{},
},
},
},
}
func LocalManifest() app.Manifest {
return app.NewEmbeddedManifest(appManifestData)
}
func RemoteManifest() app.Manifest {
return app.NewAPIServerManifest("annotation")
}
var kindVersionToGoType = map[string]resource.Kind{
"Annotation/v0alpha1": v0alpha1.AnnotationKind(),
}
// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists.
// If there is no association for the provided Kind and Version, exists will return false.
func ManifestGoTypeAssociator(kind, version string) (goType resource.Kind, exists bool) {
goType, exists = kindVersionToGoType[fmt.Sprintf("%s/%s", kind, version)]
return goType, exists
}
var customRouteToGoResponseType = map[string]any{}
// ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists.
// kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths.
// If there is no association for the provided kind, version, custom route path, and method, exists will return false.
// Resource routes (those without a kind) should prefix their route with "<namespace>/" if the route is namespaced (otherwise the route is assumed to be cluster-scope)
func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) {
if len(path) > 0 && path[0] == '/' {
path = path[1:]
}
goType, exists = customRouteToGoResponseType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
return goType, exists
}
var customRouteToGoParamsType = map[string]runtime.Object{}
func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goType runtime.Object, exists bool) {
if len(path) > 0 && path[0] == '/' {
path = path[1:]
}
goType, exists = customRouteToGoParamsType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
return goType, exists
}
var customRouteToGoRequestBodyType = map[string]any{}
func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) {
if len(path) > 0 && path[0] == '/' {
path = path[1:]
}
goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))]
return goType, exists
}
type GoTypeAssociator struct{}
func NewGoTypeAssociator() *GoTypeAssociator {
return &GoTypeAssociator{}
}
func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) {
return ManifestGoTypeAssociator(kind, version)
}
func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb string) (goType any, exists bool) {
return ManifestCustomRouteResponsesAssociator(kind, version, path, verb)
}
func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) {
return ManifestCustomRouteQueryAssociator(kind, version, path, verb)
}
func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) {
return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb)
}

View File

@@ -0,0 +1,54 @@
package app
import (
"context"
"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"
annotationv0alpha1 "github.com/grafana/grafana/apps/annotation/pkg/apis/annotation/v0alpha1"
)
func New(cfg app.Config) (app.App, error) {
simpleConfig := simple.AppConfig{
Name: "annotation",
KubeConfig: cfg.KubeConfig,
InformerConfig: simple.AppInformerConfig{
InformerOptions: operator.InformerOptions{
ErrorHandler: func(ctx context.Context, err error) {
logging.FromContext(ctx).Error("Informer processing error", "error", err)
},
},
},
ManagedKinds: []simple.AppManagedKind{{
Kind: annotationv0alpha1.AnnotationKind(),
},
},
}
a, err := simple.NewApp(simpleConfig)
if err != nil {
return nil, err
}
err = a.ValidateManifest(cfg.ManifestData)
if err != nil {
return nil, err
}
return a, nil
}
func GetKinds() map[schema.GroupVersion][]resource.Kind {
gv := schema.GroupVersion{
Group: annotationv0alpha1.AnnotationKind().Group(),
Version: annotationv0alpha1.AnnotationKind().Version(),
}
return map[schema.GroupVersion][]resource.Kind{
gv: {annotationv0alpha1.AnnotationKind()},
}
}

View File

@@ -0,0 +1,49 @@
/*
* This file was generated by grafana-app-sdk. DO NOT EDIT.
*/
import { Spec } from './types.spec.gen';
import { Status } from './types.status.gen';
export interface Metadata {
name: string;
namespace: string;
generateName?: string;
selfLink?: string;
uid?: string;
resourceVersion?: string;
generation?: number;
creationTimestamp?: string;
deletionTimestamp?: string;
deletionGracePeriodSeconds?: number;
labels?: Record<string, string>;
annotations?: Record<string, string>;
ownerReferences?: OwnerReference[];
finalizers?: string[];
managedFields?: ManagedFieldsEntry[];
}
export interface OwnerReference {
apiVersion: string;
kind: string;
name: string;
uid: string;
controller?: boolean;
blockOwnerDeletion?: boolean;
}
export interface ManagedFieldsEntry {
manager?: string;
operation?: string;
apiVersion?: string;
time?: string;
fieldsType?: string;
subresource?: string;
}
export interface Annotation {
kind: string;
apiVersion: string;
metadata: Metadata;
spec: Spec;
status: Status;
}

View File

@@ -0,0 +1,30 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
// metadata contains embedded CommonMetadata and can be extended with custom string fields
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
// without external reference as using the CommonMetadata reference breaks thema codegen.
export interface Metadata {
updateTimestamp: string;
createdBy: string;
uid: string;
creationTimestamp: string;
deletionTimestamp?: string;
finalizers: string[];
resourceVersion: string;
generation: number;
updatedBy: string;
labels: Record<string, string>;
}
export const defaultMetadata = (): Metadata => ({
updateTimestamp: "",
createdBy: "",
uid: "",
creationTimestamp: "",
finalizers: [],
resourceVersion: "",
generation: 0,
updatedBy: "",
labels: {},
});

View File

@@ -0,0 +1,16 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
export interface Spec {
text: string;
time: number;
timeEnd?: number;
dashboardUID?: string;
panelID?: number;
tags?: string[];
}
export const defaultSpec = (): Spec => ({
text: "",
time: 0,
});

View File

@@ -0,0 +1,30 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
export interface OperatorState {
// lastEvaluation is the ResourceVersion last evaluated
lastEvaluation: string;
// state describes the state of the lastEvaluation.
// It is limited to three possible states for machine evaluation.
state: "success" | "in_progress" | "failed";
// descriptiveState is an optional more descriptive state field which has no requirements on format
descriptiveState?: string;
// details contains any extra information that is operator-specific
details?: Record<string, any>;
}
export const defaultOperatorState = (): OperatorState => ({
lastEvaluation: "",
state: "success",
});
export interface Status {
// operatorStates is a map of operator ID to operator state evaluations.
// Any operator which consumes this kind SHOULD add its state evaluation information to this field.
operatorStates?: Record<string, OperatorState>;
// additionalFields is reserved for future use
additionalFields?: Record<string, any>;
}
export const defaultStatus = (): Status => ({
});

File diff suppressed because one or more lines are too long

View File

@@ -100,24 +100,24 @@ require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/at-wat/mqtt-go v0.19.4 // indirect
github.com/aws/aws-sdk-go v1.55.7 // indirect
github.com/aws/aws-sdk-go-v2 v1.38.1 // indirect
github.com/aws/aws-sdk-go-v2 v1.39.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
github.com/aws/aws-sdk-go-v2/config v1.31.2 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.6 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 // indirect
github.com/aws/aws-sdk-go-v2/config v1.31.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.29.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect
github.com/aws/smithy-go v1.23.1 // indirect
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
@@ -151,7 +151,6 @@ require (
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dennwc/varint v1.0.0 // indirect
github.com/dgraph-io/badger/v4 v4.7.0 // indirect
@@ -182,7 +181,7 @@ require (
github.com/go-jose/go-jose/v4 v4.1.2 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-ldap/ldap/v3 v3.4.4 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logfmt/logfmt v0.6.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/analysis v0.24.0 // indirect
@@ -235,7 +234,7 @@ require (
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
github.com/grafana/grafana-aws-sdk v1.3.0 // indirect
github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // indirect
github.com/grafana/grafana-plugin-sdk-go v0.281.0 // indirect
github.com/grafana/grafana/apps/dashboard v0.0.0 // indirect
@@ -295,7 +294,6 @@ require (
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/lestrrat-go/strftime v1.0.4 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magefile/mage v1.15.0 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38 // indirect
github.com/mattetti/filebuffer v1.0.1 // indirect
@@ -361,7 +359,6 @@ require (
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
github.com/sethvargo/go-retry v0.3.0 // indirect
@@ -382,13 +379,9 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tetratelabs/wazero v1.8.2 // indirect
github.com/thomaspoignant/go-feature-flag v1.42.0 // indirect
github.com/tjhop/slog-gokit v0.1.3 // indirect
github.com/tjhop/slog-gokit v0.1.5 // indirect
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 // indirect
github.com/unknwon/com v1.0.1 // indirect
github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a // indirect
github.com/urfave/cli v1.22.17 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/woodsbury/decimal128 v1.3.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
@@ -455,7 +448,6 @@ require (
google.golang.org/protobuf v1.36.10 // 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
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/mail.v2 v2.3.1 // indirect

View File

@@ -237,22 +237,22 @@ github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8=
github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE=
github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
github.com/aws/aws-sdk-go-v2/config v1.31.2 h1:NOaSZpVGEH2Np/c1toSeW0jooNl+9ALmsUTZ8YvkJR0=
github.com/aws/aws-sdk-go-v2/config v1.31.2/go.mod h1:17ft42Yb2lF6OigqSYiDAiUcX4RIkEMY6XxEMJsrAes=
github.com/aws/aws-sdk-go-v2/credentials v1.18.6 h1:AmmvNEYrru7sYNJnp3pf57lGbiarX4T9qU/6AZ9SucU=
github.com/aws/aws-sdk-go-v2/credentials v1.18.6/go.mod h1:/jdQkh1iVPa01xndfECInp1v1Wnp70v3K4MvtlLGVEc=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 h1:lpdMwTzmuDLkgW7086jE94HweHCqG+uOJwHf3LZs7T0=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY=
github.com/aws/aws-sdk-go-v2/config v1.31.10 h1:7LllDZAegXU3yk41mwM6KcPu0wmjKGQB1bg99bNdQm4=
github.com/aws/aws-sdk-go-v2/config v1.31.10/go.mod h1:Ge6gzXPjqu4v0oHvgAwvGzYcK921GU0hQM25WF/Kl+8=
github.com/aws/aws-sdk-go-v2/credentials v1.18.14 h1:TxkI7QI+sFkTItN/6cJuMZEIVMFXeu2dI1ZffkXngKI=
github.com/aws/aws-sdk-go-v2/credentials v1.18.14/go.mod h1:12x4Uw/vijC11XkctTjy92TNCQ+UnNJkT7fzX0Yd93E=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8 h1:gLD09eaJUdiszm7vd1btiQUYE0Hj+0I2b8AS+75z9AY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8/go.mod h1:4RW3oMPt1POR74qVOC4SbubxAwdP4pCT0nSw3jycOU4=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 h1:cTXRdLkpBanlDwISl+5chq5ui1d1YWg4PWMR9c3kXyw=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84/go.mod h1:kwSy5X7tfIHN39uucmjQVs2LvDdXEjQucgQQEqCggEo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 h1:IdCLsiiIj5YJ3AFevsewURCPV+YWUlOW8JiPhoAy8vg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 h1:j7vjtr1YIssWQOMeOWRbh3z8g2oY/xPjnZH2gLY4sGw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8/go.mod h1:KcGkXFVU8U28qS4KvLEcPxytPZPBcRawaH2Pf/0jptE=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8/go.mod h1:JnA+hPWeYAVbDssp83tv+ysAG8lTfLVXvSsyKg/7xNA=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs=
@@ -263,12 +263,12 @@ github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 h1:e5cbPZYTIY2nUEFie
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0/go.mod h1:UseIHRfrm7PqeZo6fcTb6FUCXzCnh1KJbQbmOfxArGM=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 h1:IfMb3Ar8xEaWjgH/zeVHYD8izwJdQgRP5mKCTDt4GNk=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2/go.mod h1:35jGWx7ECvCwTsApqicFYzZ7JFEnBc6oHUuOQ3xIS54=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0NacDMnaC+68za9jLwkjzxGWm0KB5HTUHjLTI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8/go.mod h1:Fw+MyTwlwjFsSTE31mH211Np+CUslml8mzc0AFEG09s=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
github.com/aws/aws-sdk-go-v2/service/kms v1.41.2 h1:zJeUxFP7+XP52u23vrp4zMcVhShTWbNO8dHV6xCSvFo=
@@ -279,12 +279,12 @@ github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6 h1:Pwbxovp
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6/go.mod h1:Z4xLt5mXspLKjBV92i165wAJ/3T6TIv4n7RtIS8pWV0=
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 h1:0reDqfEN+tB+sozj2r92Bep8MEwBZgtAXTND1Kk9OXg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 h1:ve9dYBB8CfJGTFqcQ3ZLAAb/KXWgYlgu/2R2TZL2Ko0=
github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 h1:pd9G9HQaM6UZAZh19pYOkpKSQkyQQ9ftnl/LttQOcGI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.4 h1:FTdEN9dtWPB0EOURNtDPmwGp6GGvMqRJCAihkSl/1No=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.4/go.mod h1:mYubxV9Ff42fZH4kexj43gFPhgc/LyC7KqvUKt1watc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 h1:I7ghctfGXrscr7r1Ga/mDqSJKm7Fkpl5Mwq79Z+rZqU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0/go.mod h1:Zo9id81XP6jbayIFWNuDpA6lMBWhsVy+3ou2jLa4JnA=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 h1:+LVB0xBqEgjQoqr9bGZbRzvg212B0f17JdflleJRNR4=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5/go.mod h1:xoaxeqnnUaZjPjaICgIy5B+MHCSb/ZSOn4MvkFNOUA0=
github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M=
github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f/go.mod h1:2stgcRjl6QmW+gU2h5E7BQXg4HU0gzxKWDuT5HviN9s=
@@ -444,8 +444,8 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
@@ -595,8 +595,8 @@ github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXg
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
@@ -826,9 +826,6 @@ github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK
github.com/googleapis/gax-go/v2 v2.15.0 h1:SyjDc1mGgZU5LncH8gimWo9lW1DtIfPibOG81vgd/bo=
github.com/googleapis/gax-go/v2 v2.15.0/go.mod h1:zVVkkxAQHa1RQpg9z2AUCMnKhi0Qld9rcmyfL1OZhoc=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -858,8 +855,8 @@ github.com/grafana/grafana-app-sdk v0.48.1 h1:bKJadWH18WCpJ+Zk8AezRFXCcZgGredRv+
github.com/grafana/grafana-app-sdk v0.48.1/go.mod h1:5LljCz+wvmGfkQ8ZKTOfserhtXNEF0cSFthoWShvN6c=
github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng=
github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
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-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM=
github.com/grafana/grafana-aws-sdk v1.3.0/go.mod h1:VGycF0JkCGKND2O5je1ucOqPJ0ZNhZYzV3c2bNBAaGk=
github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk=
github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1/go.mod h1:Oi4anANlCuTCc66jCyqIzfVbgLXFll8Wja+Y4vfANlc=
github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 h1:JOzchPgptwJdruYoed7x28lFDwhzs7kssResYsnC0iI=
@@ -882,8 +879,8 @@ github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLm
github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000/go.mod h1:/ZklAgE1i4f3Z8uriXwESmCr1VLF8lBGaJspuaGuf78=
github.com/grafana/loki/v3 v3.2.1 h1:VB7u+KHfvL5aHAxgoVBvz5wVhsdGuqKC7uuOFOOe7jw=
github.com/grafana/loki/v3 v3.2.1/go.mod h1:WvdLl6wOS+yahaeQY+xhD2m2XzkHDfKr5FZaX7D/X2Y=
github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 h1:cS0SlJGIlZbmDLctNj5vIYGemrJDLy25wwoiIyZWVN8=
github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA=
github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b h1:rFjoqJFb2KxJ29K9ltuWRSsdA46SbN0GCxoQc36h5kg=
github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA=
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 h1:aXfUhVN/Ewfpbko2CCtL65cIiGgwStOo4lWH2b6gw2U=
@@ -1067,9 +1064,6 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6 h1:SwcnSwBR7X/5EHJQlXBockkJVIMRVt5yKaesBPMtyZQ=
github.com/jszwedko/go-datemath v0.1.1-0.20230526204004-640a500621d6/go.mod h1:WrYiIuiXUMIvTDAQw97C+9l0CnBmCcvosPjN3XDqS/o=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes=
@@ -1124,8 +1118,6 @@ github.com/m3db/prometheus_remote_client_golang v0.4.4 h1:DsAIjVKoCp7Ym35tAOFL1O
github.com/m3db/prometheus_remote_client_golang v0.4.4/go.mod h1:wHfVbA3eAK6dQvKjCkHhusWYegCk3bDGkA15zymSHdc=
github.com/madflojo/testcerts v1.4.0 h1:I09gN0C1ly9IgeVNcAqKk8RAKIJTe3QnFrrPBDyvzN4=
github.com/madflojo/testcerts v1.4.0/go.mod h1:MW8sh39gLnkKh4K0Nc55AyHEDl9l/FBLDUsQhpmkuo0=
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
@@ -1426,8 +1418,8 @@ github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russellhaering/goxmldsig v1.4.0 h1:8UcDh/xGyQiyrW+Fq5t8f+l2DLB1+zlhYzkPUJ7Qhys=
github.com/russellhaering/goxmldsig v1.4.0/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw=
github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@@ -1458,7 +1450,6 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs=
github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 h1:OfRzdxCzDhp+rsKWXuOO2I/quKMJ/+TQwVbIP/gltZg=
github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92/go.mod h1:7/OT02F6S6I7v6WXb+IjhMuZEYfH/RJ5RwEWnEo5BMg=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
@@ -1467,11 +1458,6 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/sony/gobreaker v0.5.0 h1:dRCvqm0P490vZPmy7ppEk2qCnCieBooFJ+YoXGYB+yg=
@@ -1526,7 +1512,6 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
@@ -1541,8 +1526,8 @@ github.com/thejerf/slogassert v0.3.4/go.mod h1:0zn9ISLVKo1aPMTqcGfG1o6dWwt+Rk574
github.com/thomaspoignant/go-feature-flag v1.42.0 h1:C7embmOTzaLyRki+OoU2RvtVjJE9IrvgBA2C1mRN1lc=
github.com/thomaspoignant/go-feature-flag v1.42.0/go.mod h1:y0QiWH7chHWhGATb/+XqwAwErORmPSH2MUsQlCmmWlM=
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o=
github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs=
github.com/tjhop/slog-gokit v0.1.5 h1:ayloIUi5EK2QYB8eY4DOPO95/mRtMW42lUkp3quJohc=
github.com/tjhop/slog-gokit v0.1.5/go.mod h1:yA48zAHvV+Sg4z4VRyeFyFUNNXd3JY5Zg84u3USICq0=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
@@ -1559,16 +1544,7 @@ github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI=
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4=
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/unknwon/log v0.0.0-20150304194804-e617c87089d3/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU=
github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a h1:vcrhXnj9g9PIE+cmZgaPSwOyJ8MAQTRmsgGrB0x5rF4=
github.com/unknwon/log v0.0.0-20200308114134-929b1006e34a/go.mod h1:1xEUf2abjfP92w2GZTV+GgaRxXErwRXcClbUwrNJffU=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@@ -1916,7 +1892,6 @@ golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191020152052-9984515f0562/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -2293,8 +2268,6 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/fsnotify/fsnotify.v1 v1.4.7 h1:XNNYLJHt73EyYiCZi6+xjupS9CpvmiDgjPTAjrBlQbo=
gopkg.in/fsnotify/fsnotify.v1 v1.4.7/go.mod h1:Fyux9zXlo4rWoMSIzpn9fDAYjalPqJ/K1qJ27s+7ltE=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=

View File

@@ -0,0 +1,20 @@
package kinds
import (
"github.com/grafana/grafana/apps/iam/kinds/v0alpha1"
)
externalGroupMappingKind: {
kind: "ExternalGroupMapping"
pluralName: "ExternalGroupMappings"
codegen: {
ts: {enabled: false}
go: {enabled: true}
}
}
externalGroupMappingv0alpha1: externalGroupMappingKind & {
schema: {
spec: v0alpha1.ExternalGroupMappingSpec
}
}

View File

@@ -20,5 +20,6 @@ v0alpha1: {
teamv0alpha1,
teambindingv0alpha1,
serviceaccountv0alpha1,
externalGroupMappingv0alpha1
]
}

View File

@@ -0,0 +1,6 @@
package v0alpha1
ExternalGroupMappingSpec: {
teamRef: TeamRef
externalGroupId: string
}

View File

@@ -0,0 +1,80 @@
package v0alpha1
import (
"context"
"github.com/grafana/grafana-app-sdk/resource"
)
type ExternalGroupMappingClient struct {
client *resource.TypedClient[*ExternalGroupMapping, *ExternalGroupMappingList]
}
func NewExternalGroupMappingClient(client resource.Client) *ExternalGroupMappingClient {
return &ExternalGroupMappingClient{
client: resource.NewTypedClient[*ExternalGroupMapping, *ExternalGroupMappingList](client, ExternalGroupMappingKind()),
}
}
func NewExternalGroupMappingClientFromGenerator(generator resource.ClientGenerator) (*ExternalGroupMappingClient, error) {
c, err := generator.ClientFor(ExternalGroupMappingKind())
if err != nil {
return nil, err
}
return NewExternalGroupMappingClient(c), nil
}
func (c *ExternalGroupMappingClient) Get(ctx context.Context, identifier resource.Identifier) (*ExternalGroupMapping, error) {
return c.client.Get(ctx, identifier)
}
func (c *ExternalGroupMappingClient) List(ctx context.Context, namespace string, opts resource.ListOptions) (*ExternalGroupMappingList, error) {
return c.client.List(ctx, namespace, opts)
}
func (c *ExternalGroupMappingClient) ListAll(ctx context.Context, namespace string, opts resource.ListOptions) (*ExternalGroupMappingList, 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 *ExternalGroupMappingClient) Create(ctx context.Context, obj *ExternalGroupMapping, opts resource.CreateOptions) (*ExternalGroupMapping, error) {
// Make sure apiVersion and kind are set
obj.APIVersion = GroupVersion.Identifier()
obj.Kind = ExternalGroupMappingKind().Kind()
return c.client.Create(ctx, obj, opts)
}
func (c *ExternalGroupMappingClient) Update(ctx context.Context, obj *ExternalGroupMapping, opts resource.UpdateOptions) (*ExternalGroupMapping, error) {
return c.client.Update(ctx, obj, opts)
}
func (c *ExternalGroupMappingClient) Patch(ctx context.Context, identifier resource.Identifier, req resource.PatchRequest, opts resource.PatchOptions) (*ExternalGroupMapping, error) {
return c.client.Patch(ctx, identifier, req, opts)
}
func (c *ExternalGroupMappingClient) Delete(ctx context.Context, identifier resource.Identifier, opts resource.DeleteOptions) error {
return c.client.Delete(ctx, identifier, opts)
}

View File

@@ -0,0 +1,28 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"encoding/json"
"io"
"github.com/grafana/grafana-app-sdk/resource"
)
// ExternalGroupMappingJSONCodec is an implementation of resource.Codec for kubernetes JSON encoding
type ExternalGroupMappingJSONCodec struct{}
// Read reads JSON-encoded bytes from `reader` and unmarshals them into `into`
func (*ExternalGroupMappingJSONCodec) Read(reader io.Reader, into resource.Object) error {
return json.NewDecoder(reader).Decode(into)
}
// Write writes JSON-encoded bytes into `writer` marshaled from `from`
func (*ExternalGroupMappingJSONCodec) Write(writer io.Writer, from resource.Object) error {
return json.NewEncoder(writer).Encode(from)
}
// Interface compliance checks
var _ resource.Codec = &ExternalGroupMappingJSONCodec{}

View File

@@ -0,0 +1,31 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
import (
time "time"
)
// metadata contains embedded CommonMetadata and can be extended with custom string fields
// TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
// without external reference as using the CommonMetadata reference breaks thema codegen.
type ExternalGroupMappingMetadata struct {
UpdateTimestamp time.Time `json:"updateTimestamp"`
CreatedBy string `json:"createdBy"`
Uid string `json:"uid"`
CreationTimestamp time.Time `json:"creationTimestamp"`
DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"`
Finalizers []string `json:"finalizers"`
ResourceVersion string `json:"resourceVersion"`
Generation int64 `json:"generation"`
UpdatedBy string `json:"updatedBy"`
Labels map[string]string `json:"labels"`
}
// NewExternalGroupMappingMetadata creates a new ExternalGroupMappingMetadata object.
func NewExternalGroupMappingMetadata() *ExternalGroupMappingMetadata {
return &ExternalGroupMappingMetadata{
Finalizers: []string{},
Labels: map[string]string{},
}
}

View File

@@ -0,0 +1,293 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"fmt"
"github.com/grafana/grafana-app-sdk/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"time"
)
// +k8s:openapi-gen=true
type ExternalGroupMapping struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ObjectMeta `json:"metadata" yaml:"metadata"`
// Spec is the spec of the ExternalGroupMapping
Spec ExternalGroupMappingSpec `json:"spec" yaml:"spec"`
}
func (o *ExternalGroupMapping) GetSpec() any {
return o.Spec
}
func (o *ExternalGroupMapping) SetSpec(spec any) error {
cast, ok := spec.(ExternalGroupMappingSpec)
if !ok {
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
}
o.Spec = cast
return nil
}
func (o *ExternalGroupMapping) GetSubresources() map[string]any {
return map[string]any{}
}
func (o *ExternalGroupMapping) GetSubresource(name string) (any, bool) {
switch name {
default:
return nil, false
}
}
func (o *ExternalGroupMapping) SetSubresource(name string, value any) error {
switch name {
default:
return fmt.Errorf("subresource '%s' does not exist", name)
}
}
func (o *ExternalGroupMapping) GetStaticMetadata() resource.StaticMetadata {
gvk := o.GroupVersionKind()
return resource.StaticMetadata{
Name: o.ObjectMeta.Name,
Namespace: o.ObjectMeta.Namespace,
Group: gvk.Group,
Version: gvk.Version,
Kind: gvk.Kind,
}
}
func (o *ExternalGroupMapping) SetStaticMetadata(metadata resource.StaticMetadata) {
o.Name = metadata.Name
o.Namespace = metadata.Namespace
o.SetGroupVersionKind(schema.GroupVersionKind{
Group: metadata.Group,
Version: metadata.Version,
Kind: metadata.Kind,
})
}
func (o *ExternalGroupMapping) GetCommonMetadata() resource.CommonMetadata {
dt := o.DeletionTimestamp
var deletionTimestamp *time.Time
if dt != nil {
deletionTimestamp = &dt.Time
}
// Legacy ExtraFields support
extraFields := make(map[string]any)
if o.Annotations != nil {
extraFields["annotations"] = o.Annotations
}
if o.ManagedFields != nil {
extraFields["managedFields"] = o.ManagedFields
}
if o.OwnerReferences != nil {
extraFields["ownerReferences"] = o.OwnerReferences
}
return resource.CommonMetadata{
UID: string(o.UID),
ResourceVersion: o.ResourceVersion,
Generation: o.Generation,
Labels: o.Labels,
CreationTimestamp: o.CreationTimestamp.Time,
DeletionTimestamp: deletionTimestamp,
Finalizers: o.Finalizers,
UpdateTimestamp: o.GetUpdateTimestamp(),
CreatedBy: o.GetCreatedBy(),
UpdatedBy: o.GetUpdatedBy(),
ExtraFields: extraFields,
}
}
func (o *ExternalGroupMapping) SetCommonMetadata(metadata resource.CommonMetadata) {
o.UID = types.UID(metadata.UID)
o.ResourceVersion = metadata.ResourceVersion
o.Generation = metadata.Generation
o.Labels = metadata.Labels
o.CreationTimestamp = metav1.NewTime(metadata.CreationTimestamp)
if metadata.DeletionTimestamp != nil {
dt := metav1.NewTime(*metadata.DeletionTimestamp)
o.DeletionTimestamp = &dt
} else {
o.DeletionTimestamp = nil
}
o.Finalizers = metadata.Finalizers
if o.Annotations == nil {
o.Annotations = make(map[string]string)
}
if !metadata.UpdateTimestamp.IsZero() {
o.SetUpdateTimestamp(metadata.UpdateTimestamp)
}
if metadata.CreatedBy != "" {
o.SetCreatedBy(metadata.CreatedBy)
}
if metadata.UpdatedBy != "" {
o.SetUpdatedBy(metadata.UpdatedBy)
}
// Legacy support for setting Annotations, ManagedFields, and OwnerReferences via ExtraFields
if metadata.ExtraFields != nil {
if annotations, ok := metadata.ExtraFields["annotations"]; ok {
if cast, ok := annotations.(map[string]string); ok {
o.Annotations = cast
}
}
if managedFields, ok := metadata.ExtraFields["managedFields"]; ok {
if cast, ok := managedFields.([]metav1.ManagedFieldsEntry); ok {
o.ManagedFields = cast
}
}
if ownerReferences, ok := metadata.ExtraFields["ownerReferences"]; ok {
if cast, ok := ownerReferences.([]metav1.OwnerReference); ok {
o.OwnerReferences = cast
}
}
}
}
func (o *ExternalGroupMapping) GetCreatedBy() string {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
}
func (o *ExternalGroupMapping) SetCreatedBy(createdBy string) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
}
func (o *ExternalGroupMapping) GetUpdateTimestamp() time.Time {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
return parsed
}
func (o *ExternalGroupMapping) SetUpdateTimestamp(updateTimestamp time.Time) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
}
func (o *ExternalGroupMapping) GetUpdatedBy() string {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
}
func (o *ExternalGroupMapping) SetUpdatedBy(updatedBy string) {
if o.ObjectMeta.Annotations == nil {
o.ObjectMeta.Annotations = make(map[string]string)
}
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
}
func (o *ExternalGroupMapping) Copy() resource.Object {
return resource.CopyObject(o)
}
func (o *ExternalGroupMapping) DeepCopyObject() runtime.Object {
return o.Copy()
}
func (o *ExternalGroupMapping) DeepCopy() *ExternalGroupMapping {
cpy := &ExternalGroupMapping{}
o.DeepCopyInto(cpy)
return cpy
}
func (o *ExternalGroupMapping) DeepCopyInto(dst *ExternalGroupMapping) {
dst.TypeMeta.APIVersion = o.TypeMeta.APIVersion
dst.TypeMeta.Kind = o.TypeMeta.Kind
o.ObjectMeta.DeepCopyInto(&dst.ObjectMeta)
o.Spec.DeepCopyInto(&dst.Spec)
}
// Interface compliance compile-time check
var _ resource.Object = &ExternalGroupMapping{}
// +k8s:openapi-gen=true
type ExternalGroupMappingList struct {
metav1.TypeMeta `json:",inline" yaml:",inline"`
metav1.ListMeta `json:"metadata" yaml:"metadata"`
Items []ExternalGroupMapping `json:"items" yaml:"items"`
}
func (o *ExternalGroupMappingList) DeepCopyObject() runtime.Object {
return o.Copy()
}
func (o *ExternalGroupMappingList) Copy() resource.ListObject {
cpy := &ExternalGroupMappingList{
TypeMeta: o.TypeMeta,
Items: make([]ExternalGroupMapping, len(o.Items)),
}
o.ListMeta.DeepCopyInto(&cpy.ListMeta)
for i := 0; i < len(o.Items); i++ {
if item, ok := o.Items[i].Copy().(*ExternalGroupMapping); ok {
cpy.Items[i] = *item
}
}
return cpy
}
func (o *ExternalGroupMappingList) GetItems() []resource.Object {
items := make([]resource.Object, len(o.Items))
for i := 0; i < len(o.Items); i++ {
items[i] = &o.Items[i]
}
return items
}
func (o *ExternalGroupMappingList) SetItems(items []resource.Object) {
o.Items = make([]ExternalGroupMapping, len(items))
for i := 0; i < len(items); i++ {
o.Items[i] = *items[i].(*ExternalGroupMapping)
}
}
func (o *ExternalGroupMappingList) DeepCopy() *ExternalGroupMappingList {
cpy := &ExternalGroupMappingList{}
o.DeepCopyInto(cpy)
return cpy
}
func (o *ExternalGroupMappingList) DeepCopyInto(dst *ExternalGroupMappingList) {
resource.CopyObjectInto(dst, o)
}
// Interface compliance compile-time check
var _ resource.ListObject = &ExternalGroupMappingList{}
// Copy methods for all subresource types
// DeepCopy creates a full deep copy of Spec
func (s *ExternalGroupMappingSpec) DeepCopy() *ExternalGroupMappingSpec {
cpy := &ExternalGroupMappingSpec{}
s.DeepCopyInto(cpy)
return cpy
}
// DeepCopyInto deep copies Spec into another Spec object
func (s *ExternalGroupMappingSpec) DeepCopyInto(dst *ExternalGroupMappingSpec) {
resource.CopyObjectInto(dst, s)
}

View File

@@ -0,0 +1,34 @@
//
// Code generated by grafana-app-sdk. DO NOT EDIT.
//
package v0alpha1
import (
"github.com/grafana/grafana-app-sdk/resource"
)
// schema is unexported to prevent accidental overwrites
var (
schemaExternalGroupMapping = resource.NewSimpleSchema("iam.grafana.app", "v0alpha1", &ExternalGroupMapping{}, &ExternalGroupMappingList{}, resource.WithKind("ExternalGroupMapping"),
resource.WithPlural("externalgroupmappings"), resource.WithScope(resource.NamespacedScope))
kindExternalGroupMapping = resource.Kind{
Schema: schemaExternalGroupMapping,
Codecs: map[resource.KindEncoding]resource.Codec{
resource.KindEncodingJSON: &ExternalGroupMappingJSONCodec{},
},
}
)
// Kind returns a resource.Kind for this Schema with a JSON codec
func ExternalGroupMappingKind() resource.Kind {
return kindExternalGroupMapping
}
// Schema returns a resource.SimpleSchema representation of ExternalGroupMapping
func ExternalGroupMappingSchema() *resource.SimpleSchema {
return schemaExternalGroupMapping
}
// Interface compliance checks
var _ resource.Schema = kindExternalGroupMapping

View File

@@ -0,0 +1,27 @@
// Code generated - EDITING IS FUTILE. DO NOT EDIT.
package v0alpha1
// +k8s:openapi-gen=true
type ExternalGroupMappingTeamRef struct {
// Name is the unique identifier for a team.
Name string `json:"name"`
}
// NewExternalGroupMappingTeamRef creates a new ExternalGroupMappingTeamRef object.
func NewExternalGroupMappingTeamRef() *ExternalGroupMappingTeamRef {
return &ExternalGroupMappingTeamRef{}
}
// +k8s:openapi-gen=true
type ExternalGroupMappingSpec struct {
TeamRef ExternalGroupMappingTeamRef `json:"teamRef"`
ExternalGroupId string `json:"externalGroupId"`
}
// NewExternalGroupMappingSpec creates a new ExternalGroupMappingSpec object.
func NewExternalGroupMappingSpec() *ExternalGroupMappingSpec {
return &ExternalGroupMappingSpec{
TeamRef: *NewExternalGroupMappingTeamRef(),
}
}

View File

@@ -206,6 +206,30 @@ var TeamBindingResourceInfo = utils.NewResourceInfo(
},
)
var ExternalGroupMappingResourceInfo = utils.NewResourceInfo(GROUP, VERSION,
"externalgroupmappings", "externalgroupmapping", "ExternalGroupMapping",
func() runtime.Object { return &ExternalGroupMapping{} },
func() runtime.Object { return &ExternalGroupMappingList{} },
utils.TableColumns{
Definition: []metav1.TableColumnDefinition{
{Name: "Name", Type: "string", Format: "name"},
{Name: "Created At", Type: "date"},
},
Reader: func(obj any) ([]interface{}, error) {
mapping, ok := obj.(*ExternalGroupMapping)
if ok {
if mapping != nil {
return []interface{}{
mapping.Name,
mapping.CreationTimestamp.UTC().Format(time.RFC3339),
}, nil
}
}
return nil, fmt.Errorf("expected external group mapping")
},
},
)
var RoleBindingInfo = utils.NewResourceInfo(GROUP, VERSION,
"rolebindings", "rolebinding", "RoleBinding",
func() runtime.Object { return &RoleBinding{} },
@@ -295,6 +319,8 @@ func AddAuthNKnownTypes(scheme *runtime.Scheme) error {
&TeamList{},
&TeamBinding{},
&TeamBindingList{},
&ExternalGroupMapping{},
&ExternalGroupMappingList{},
// For now these are registered in pkg/apis/iam/v0alpha1/register.go
// &UserTeamList{},
// &ServiceAccountTokenList{},

View File

@@ -18,6 +18,10 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRoleStatus": schema_pkg_apis_iam_v0alpha1_CoreRoleStatus(ref),
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRolespecPermission": schema_pkg_apis_iam_v0alpha1_CoreRolespecPermission(ref),
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.CoreRolestatusOperatorState": schema_pkg_apis_iam_v0alpha1_CoreRolestatusOperatorState(ref),
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMapping": schema_pkg_apis_iam_v0alpha1_ExternalGroupMapping(ref),
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingList": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingList(ref),
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingSpec": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingSpec(ref),
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingTeamRef": schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingTeamRef(ref),
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRole": schema_pkg_apis_iam_v0alpha1_GlobalRole(ref),
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBinding": schema_pkg_apis_iam_v0alpha1_GlobalRoleBinding(ref),
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.GlobalRoleBindingList": schema_pkg_apis_iam_v0alpha1_GlobalRoleBindingList(ref),
@@ -348,6 +352,145 @@ func schema_pkg_apis_iam_v0alpha1_CoreRolestatusOperatorState(ref common.Referen
}
}
func schema_pkg_apis_iam_v0alpha1_ExternalGroupMapping(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{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
},
},
"spec": {
SchemaProps: spec.SchemaProps{
Description: "Spec is the spec of the ExternalGroupMapping",
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingSpec"),
},
},
},
Required: []string{"metadata", "spec"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
func schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingList(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{
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
Type: []string{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
Type: []string{"string"},
Format: "",
},
},
"metadata": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
},
},
"items": {
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.ExternalGroupMapping"),
},
},
},
},
},
},
Required: []string{"metadata", "items"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMapping", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
}
}
func schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"teamRef": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingTeamRef"),
},
},
"externalGroupId": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"teamRef", "externalGroupId"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/iam/pkg/apis/iam/v0alpha1.ExternalGroupMappingTeamRef"},
}
}
func schema_pkg_apis_iam_v0alpha1_ExternalGroupMappingTeamRef(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"name": {
SchemaProps: spec.SchemaProps{
Description: "Name is the unique identifier for a team.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"name"},
},
},
}
}
func schema_pkg_apis_iam_v0alpha1_GlobalRole(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{

View File

@@ -96,6 +96,13 @@ var appManifestData = app.ManifestData{
Scope: "Namespaced",
Conversion: false,
},
{
Kind: "ExternalGroupMapping",
Plural: "ExternalGroupMappings",
Scope: "Namespaced",
Conversion: false,
},
},
Routes: app.ManifestVersionRoutes{
Namespaced: map[string]spec3.PathProps{},
@@ -115,16 +122,17 @@ func RemoteManifest() app.Manifest {
}
var kindVersionToGoType = map[string]resource.Kind{
"GlobalRole/v0alpha1": v0alpha1.GlobalRoleKind(),
"GlobalRoleBinding/v0alpha1": v0alpha1.GlobalRoleBindingKind(),
"CoreRole/v0alpha1": v0alpha1.CoreRoleKind(),
"Role/v0alpha1": v0alpha1.RoleKind(),
"RoleBinding/v0alpha1": v0alpha1.RoleBindingKind(),
"ResourcePermission/v0alpha1": v0alpha1.ResourcePermissionKind(),
"User/v0alpha1": v0alpha1.UserKind(),
"Team/v0alpha1": v0alpha1.TeamKind(),
"TeamBinding/v0alpha1": v0alpha1.TeamBindingKind(),
"ServiceAccount/v0alpha1": v0alpha1.ServiceAccountKind(),
"GlobalRole/v0alpha1": v0alpha1.GlobalRoleKind(),
"GlobalRoleBinding/v0alpha1": v0alpha1.GlobalRoleBindingKind(),
"CoreRole/v0alpha1": v0alpha1.CoreRoleKind(),
"Role/v0alpha1": v0alpha1.RoleKind(),
"RoleBinding/v0alpha1": v0alpha1.RoleBindingKind(),
"ResourcePermission/v0alpha1": v0alpha1.ResourcePermissionKind(),
"User/v0alpha1": v0alpha1.UserKind(),
"Team/v0alpha1": v0alpha1.TeamKind(),
"TeamBinding/v0alpha1": v0alpha1.TeamBindingKind(),
"ServiceAccount/v0alpha1": v0alpha1.ServiceAccountKind(),
"ExternalGroupMapping/v0alpha1": v0alpha1.ExternalGroupMappingKind(),
}
// ManifestGoTypeAssociator returns the associated resource.Kind instance for a given Kind and Version, if one exists.

View File

@@ -10,7 +10,7 @@ require (
github.com/grafana/grafana-app-sdk/logging v0.48.1
github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2
github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0
github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b
github.com/migueleliasweb/go-github-mock v1.1.0
github.com/stretchr/testify v1.11.1
golang.org/x/oauth2 v0.32.0

View File

@@ -70,8 +70,8 @@ github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f h1:f+Z
github.com/grafana/grafana/apps/secret v0.0.0-20250902093454-b56b7add012f/go.mod h1:RA8mP8KVIwKXBx3Ssqa/uEBABib5LvUWYPVMxrNvnP0=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2 h1:X0cnaFdR+iz+sDSuoZmkryFSjOirchHe2MdKSRwBWgM=
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20250804150913-990f1c69ecc2/go.mod h1:RRvSjHH12/PnQaXraMO65jUhVu8n59mzvhfIMBETnV4=
github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 h1:cS0SlJGIlZbmDLctNj5vIYGemrJDLy25wwoiIyZWVN8=
github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA=
github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b h1:rFjoqJFb2KxJ29K9ltuWRSsdA46SbN0GCxoQc36h5kg=
github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=

View File

@@ -0,0 +1,172 @@
package jobs
import (
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/validation/field"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/apps/provisioning/pkg/repository/git"
"github.com/grafana/grafana/apps/provisioning/pkg/safepath"
)
// ValidateJob performs validation on the Job specification and returns an error if validation fails
func ValidateJob(job *provisioning.Job) error {
list := field.ErrorList{}
// Validate action is specified
if job.Spec.Action == "" {
list = append(list, field.Required(field.NewPath("spec", "action"), "action must be specified"))
return toError(job.Name, list) // Early return since we can't validate further without knowing the action
}
// Validate repository is specified
if job.Spec.Repository == "" {
list = append(list, field.Required(field.NewPath("spec", "repository"), "repository must be specified"))
}
// Validate action-specific options
switch job.Spec.Action {
case provisioning.JobActionPull:
if job.Spec.Pull == nil {
list = append(list, field.Required(field.NewPath("spec", "pull"), "pull options required for pull action"))
}
// Pull options are simple, just incremental bool - no further validation needed
case provisioning.JobActionPush:
if job.Spec.Push == nil {
list = append(list, field.Required(field.NewPath("spec", "push"), "push options required for push action"))
} else {
list = append(list, validateExportJobOptions(job.Spec.Push)...)
}
case provisioning.JobActionPullRequest:
if job.Spec.PullRequest == nil {
list = append(list, field.Required(field.NewPath("spec", "pr"), "pull request options required for pr action"))
}
// PullRequest options are mostly informational - no strict validation needed
case provisioning.JobActionMigrate:
if job.Spec.Migrate == nil {
list = append(list, field.Required(field.NewPath("spec", "migrate"), "migrate options required for migrate action"))
}
// Migrate options are simple - no further validation needed
case provisioning.JobActionDelete:
if job.Spec.Delete == nil {
list = append(list, field.Required(field.NewPath("spec", "delete"), "delete options required for delete action"))
} else {
list = append(list, validateDeleteJobOptions(job.Spec.Delete)...)
}
case provisioning.JobActionMove:
if job.Spec.Move == nil {
list = append(list, field.Required(field.NewPath("spec", "move"), "move options required for move action"))
} else {
list = append(list, validateMoveJobOptions(job.Spec.Move)...)
}
default:
list = append(list, field.Invalid(field.NewPath("spec", "action"), job.Spec.Action, "invalid action"))
}
return toError(job.Name, list)
}
// toError converts a field.ErrorList to an error, returning nil if the list is empty
func toError(name string, list field.ErrorList) error {
if len(list) == 0 {
return nil
}
return apierrors.NewInvalid(
provisioning.JobResourceInfo.GroupVersionKind().GroupKind(),
name, list)
}
// validateExportJobOptions validates export (push) job options
func validateExportJobOptions(opts *provisioning.ExportJobOptions) field.ErrorList {
list := field.ErrorList{}
// Validate branch name if specified
if opts.Branch != "" {
if !git.IsValidGitBranchName(opts.Branch) {
list = append(list, field.Invalid(field.NewPath("spec", "push", "branch"), opts.Branch, "invalid git branch name"))
}
}
// Validate path if specified
if opts.Path != "" {
if err := safepath.IsSafe(opts.Path); err != nil {
list = append(list, field.Invalid(field.NewPath("spec", "push", "path"), opts.Path, err.Error()))
}
}
return list
}
// validateDeleteJobOptions validates delete job options
func validateDeleteJobOptions(opts *provisioning.DeleteJobOptions) field.ErrorList {
list := field.ErrorList{}
// At least one of paths or resources must be specified
if len(opts.Paths) == 0 && len(opts.Resources) == 0 {
list = append(list, field.Required(field.NewPath("spec", "delete"), "at least one path or resource must be specified"))
return list
}
// Validate paths
for i, p := range opts.Paths {
if err := safepath.IsSafe(p); err != nil {
list = append(list, field.Invalid(field.NewPath("spec", "delete", "paths").Index(i), p, err.Error()))
}
}
// Validate resources
for i, r := range opts.Resources {
if r.Name == "" {
list = append(list, field.Required(field.NewPath("spec", "delete", "resources").Index(i).Child("name"), "resource name is required"))
}
if r.Kind == "" {
list = append(list, field.Required(field.NewPath("spec", "delete", "resources").Index(i).Child("kind"), "resource kind is required"))
}
}
return list
}
// validateMoveJobOptions validates move job options
func validateMoveJobOptions(opts *provisioning.MoveJobOptions) field.ErrorList {
list := field.ErrorList{}
// At least one of paths or resources must be specified
if len(opts.Paths) == 0 && len(opts.Resources) == 0 {
list = append(list, field.Required(field.NewPath("spec", "move"), "at least one path or resource must be specified"))
return list
}
// Target path is required
if opts.TargetPath == "" {
list = append(list, field.Required(field.NewPath("spec", "move", "targetPath"), "target path is required"))
} else {
if err := safepath.IsSafe(opts.TargetPath); err != nil {
list = append(list, field.Invalid(field.NewPath("spec", "move", "targetPath"), opts.TargetPath, err.Error()))
}
}
// Validate source paths
for i, p := range opts.Paths {
if err := safepath.IsSafe(p); err != nil {
list = append(list, field.Invalid(field.NewPath("spec", "move", "paths").Index(i), p, err.Error()))
}
}
// Validate resources
for i, r := range opts.Resources {
if r.Name == "" {
list = append(list, field.Required(field.NewPath("spec", "move", "resources").Index(i).Child("name"), "resource name is required"))
}
if r.Kind == "" {
list = append(list, field.Required(field.NewPath("spec", "move", "resources").Index(i).Child("kind"), "resource kind is required"))
}
}
return list
}

View File

@@ -0,0 +1,593 @@
package jobs
import (
"testing"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
)
func TestValidateJob(t *testing.T) {
tests := []struct {
name string
job *provisioning.Job
wantErr bool
validateError func(t *testing.T, err error)
}{
{
name: "valid pull job",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionPull,
Repository: "test-repo",
Pull: &provisioning.SyncJobOptions{
Incremental: true,
},
},
},
wantErr: false,
},
{
name: "missing action",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Repository: "test-repo",
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.action: Required value")
},
},
{
name: "invalid action",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobAction("invalid"),
Repository: "test-repo",
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.action: Invalid value")
},
},
{
name: "missing repository",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionPull,
Pull: &provisioning.SyncJobOptions{
Incremental: true,
},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.repository: Required value")
},
},
{
name: "pull action without pull options",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionPull,
Repository: "test-repo",
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.pull: Required value")
},
},
{
name: "push action without push options",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionPush,
Repository: "test-repo",
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.push: Required value")
},
},
{
name: "valid push job with valid branch",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionPush,
Repository: "test-repo",
Push: &provisioning.ExportJobOptions{
Branch: "main",
Message: "Test commit",
},
},
},
wantErr: false,
},
{
name: "push job with invalid branch name",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionPush,
Repository: "test-repo",
Push: &provisioning.ExportJobOptions{
Branch: "feature..branch", // Invalid: contains consecutive dots
Message: "Test commit",
},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.push.branch")
require.Contains(t, err.Error(), "invalid git branch name")
},
},
{
name: "push job with invalid path",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionPush,
Repository: "test-repo",
Push: &provisioning.ExportJobOptions{
Path: "../../../etc/passwd", // Invalid: path traversal
Message: "Test commit",
},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.push.path")
},
},
{
name: "delete action without options",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionDelete,
Repository: "test-repo",
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.delete: Required value")
},
},
{
name: "delete action without paths or resources",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionDelete,
Repository: "test-repo",
Delete: &provisioning.DeleteJobOptions{},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "at least one path or resource must be specified")
},
},
{
name: "valid delete action with paths",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionDelete,
Repository: "test-repo",
Delete: &provisioning.DeleteJobOptions{
Paths: []string{"dashboard.json", "folder/other.json"},
},
},
},
wantErr: false,
},
{
name: "valid delete action with resources",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionDelete,
Repository: "test-repo",
Delete: &provisioning.DeleteJobOptions{
Resources: []provisioning.ResourceRef{
{
Name: "my-dashboard",
Kind: "Dashboard",
},
},
},
},
},
wantErr: false,
},
{
name: "delete action with invalid path",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionDelete,
Repository: "test-repo",
Delete: &provisioning.DeleteJobOptions{
Paths: []string{"../../etc/passwd"}, // Invalid: path traversal
},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.delete.paths[0]")
},
},
{
name: "delete action with resource missing name",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionDelete,
Repository: "test-repo",
Delete: &provisioning.DeleteJobOptions{
Resources: []provisioning.ResourceRef{
{
Kind: "Dashboard",
},
},
},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.delete.resources[0].name")
},
},
{
name: "move action without options",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionMove,
Repository: "test-repo",
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.move: Required value")
},
},
{
name: "move action without paths or resources",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionMove,
Repository: "test-repo",
Move: &provisioning.MoveJobOptions{
TargetPath: "new-location/",
},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "at least one path or resource must be specified")
},
},
{
name: "move action without target path",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionMove,
Repository: "test-repo",
Move: &provisioning.MoveJobOptions{
Paths: []string{"dashboard.json"},
},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.move.targetPath: Required value")
},
},
{
name: "valid move action",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionMove,
Repository: "test-repo",
Move: &provisioning.MoveJobOptions{
Paths: []string{"old-location/dashboard.json"},
TargetPath: "new-location/",
},
},
},
wantErr: false,
},
{
name: "move action with invalid target path",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionMove,
Repository: "test-repo",
Move: &provisioning.MoveJobOptions{
Paths: []string{"dashboard.json"},
TargetPath: "../../../etc/", // Invalid: path traversal
},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.move.targetPath")
},
},
{
name: "valid migrate job",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionMigrate,
Repository: "test-repo",
Migrate: &provisioning.MigrateJobOptions{
History: true,
Message: "Migrate from legacy",
},
},
},
wantErr: false,
},
{
name: "migrate action without migrate options",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionMigrate,
Repository: "test-repo",
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.migrate: Required value")
},
},
{
name: "valid pr job",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionPullRequest,
Repository: "test-repo",
PullRequest: &provisioning.PullRequestJobOptions{
PR: 123,
Ref: "refs/pull/123/head",
},
},
},
wantErr: false,
},
{
name: "delete action with resource missing kind",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionDelete,
Repository: "test-repo",
Delete: &provisioning.DeleteJobOptions{
Resources: []provisioning.ResourceRef{
{
Name: "my-dashboard",
},
},
},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.delete.resources[0].kind")
},
},
{
name: "move action with valid resources",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionMove,
Repository: "test-repo",
Move: &provisioning.MoveJobOptions{
Resources: []provisioning.ResourceRef{
{
Name: "my-dashboard",
Kind: "Dashboard",
},
},
TargetPath: "new-location/",
},
},
},
wantErr: false,
},
{
name: "move action with resource missing kind",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionMove,
Repository: "test-repo",
Move: &provisioning.MoveJobOptions{
Resources: []provisioning.ResourceRef{
{
Name: "my-dashboard",
},
},
TargetPath: "new-location/",
},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.move.resources[0].kind")
},
},
{
name: "move action with both paths and resources",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionMove,
Repository: "test-repo",
Move: &provisioning.MoveJobOptions{
Paths: []string{"dashboard.json"},
Resources: []provisioning.ResourceRef{
{
Name: "my-dashboard",
Kind: "Dashboard",
},
},
TargetPath: "new-location/",
},
},
},
wantErr: false,
},
{
name: "move action with invalid source path",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionMove,
Repository: "test-repo",
Move: &provisioning.MoveJobOptions{
Paths: []string{"../invalid/path"},
TargetPath: "valid/target/",
},
},
},
wantErr: true,
validateError: func(t *testing.T, err error) {
require.Contains(t, err.Error(), "spec.move.paths[0]")
},
},
{
name: "delete action with both paths and resources",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionDelete,
Repository: "test-repo",
Delete: &provisioning.DeleteJobOptions{
Paths: []string{"dashboard.json"},
Resources: []provisioning.ResourceRef{
{
Name: "my-dashboard",
Kind: "Dashboard",
},
},
},
},
},
wantErr: false,
},
{
name: "push action with valid path",
job: &provisioning.Job{
ObjectMeta: metav1.ObjectMeta{
Name: "test-job",
},
Spec: provisioning.JobSpec{
Action: provisioning.JobActionPush,
Repository: "test-repo",
Push: &provisioning.ExportJobOptions{
Path: "some/valid/path",
Message: "Test commit",
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateJob(tt.job)
if tt.wantErr {
require.Error(t, err)
if tt.validateError != nil {
tt.validateError(t, err)
}
} else {
require.NoError(t, err)
}
})
}
}

View File

@@ -443,7 +443,6 @@ content_security_policy_report_only_template = """script-src 'self' 'unsafe-eval
csrf_always_check = false
# Comma-separated list of plugins ids that will be loaded inside the frontend sandbox
# Currently behind the feature flag pluginsFrontendSandbox
enable_frontend_sandbox_for_plugins =
# Comma-separated list of paths for POST/PUT URL in actions. Empty will allow anything that is not on the same origin

View File

@@ -442,7 +442,6 @@
;csrf_always_check = false
# Comma-separated list of plugins ids that will be loaded inside the frontend sandbox
# Currently behind the feature flag pluginsFrontendSandbox
;enable_frontend_sandbox_for_plugins =
# Comma-separated list of paths for POST/PUT URL in actions. Empty will allow anything that is not on the same origin

File diff suppressed because one or more lines are too long

View File

@@ -75,7 +75,7 @@ services:
GF_TRACING_OPENTELEMETRY_OTLP_PROPAGATION: jaeger,w3c
postgres:
image: postgres:16.1-alpine3.19
image: postgres:16.1-alpine3.19@sha256:17eb369d9330fe7fbdb2f705418c18823d66322584c77c2b43cc0e1851d01de7
environment:
POSTGRES_USER: grafana
POSTGRES_PASSWORD: grafana
@@ -86,7 +86,7 @@ services:
- 'alloy.logs=true'
alloy:
image: grafana/alloy:v1.11.2
image: grafana/alloy:v1.11.2@sha256:6ab34b8201f0e8b0c4346be4934c9965723af3f7f21dd9a65fd73f270f69b451
volumes:
- ./configs/alloy:/alloy-config
- /var/run/docker.sock:/var/run/docker.sock # To scrape Docker container logs
@@ -104,7 +104,7 @@ services:
- 'alloy.logs=true'
prometheus:
image: prom/prometheus:v3.7.2
image: prom/prometheus:v3.7.2@sha256:23031bfe0e74a13004252caaa74eccd0d62b6c6e7a04711d5b8bf5b7e113adc7
volumes:
- prometheus-data:/prometheus
command:
@@ -116,7 +116,7 @@ services:
- 'alloy.logs=true'
loki:
image: grafana/loki:3.5.7
image: grafana/loki:3.5.7@sha256:0eaee7bf39cc83aaef46914fb58f287d4f4c4be6ec96b86c2ed55719a75e49c8
volumes:
- loki-data:/loki
command: -config.file=/etc/loki/local-config.yaml
@@ -124,7 +124,7 @@ services:
- 'alloy.logs=true'
tempo-init:
image: busybox:1.37.0
image: busybox:1.37.0@sha256:e3652a00a2fabd16ce889f0aa32c38eec347b997e73bd09e69c962ec7f8732ee
user: root
entrypoint:
- 'chown'
@@ -134,7 +134,7 @@ services:
- tempo-data:/var/tempo
tempo:
image: grafana/tempo:2.9.0
image: grafana/tempo:2.9.0@sha256:65a5789759435f1ef696f1953258b9bbdb18eb571d5ce711ff812d2e128288a4
volumes:
- tempo-data:/var/lib/tempo
- ./configs/tempo.yaml:/etc/tempo/tempo.yaml

View File

@@ -74,6 +74,7 @@
"mostly-blank-dashboard": (import '../dev-dashboards/scenarios/mostly-blank-dashboard.json'),
"mssql_fakedata": (import '../dev-dashboards/datasource-mssql/mssql_fakedata.json'),
"mssql_unittest": (import '../dev-dashboards/datasource-mssql/mssql_unittest.json'),
"multi-lane-annotations": (import '../dev-dashboards/annotations/multi-lane-annotations.json'),
"mysql_fakedata": (import '../dev-dashboards/datasource-mysql/mysql_fakedata.json'),
"mysql_unittest": (import '../dev-dashboards/datasource-mysql/mysql_unittest.json'),
"new_features_in_v74": (import '../dev-dashboards/datasource-testdata/new_features_in_v74.json'),

140
devenv/scopes/README.md Normal file
View File

@@ -0,0 +1,140 @@
# Scopes Provisioning Script
This script generates Scopes, ScopeNodes, and ScopeNavigations for Grafana development environments.
## Usage
### Create resources
```bash
# From devenv directory
./setup.sh scopes
# Or run directly
cd scopes
go run scopes.go
```
### Delete all gdev-prefixed resources
```bash
# From devenv directory
./setup.sh undev
# Or run directly
cd scopes
go run scopes.go -clean
```
**Note about caching**: The `/find/scope_navigations` endpoint used by the UI caches ScopeNavigation results for 15 minutes. After running cleanup, deleted resources may still appear in the UI until the cache expires. The resources are actually deleted (you can verify by checking the `/scopenavigations` list endpoint), but the UI will refresh after ~15 minutes or after restarting Grafana.
Doing an `Empty Cache and Hard Reload` will also help.
## Configuration
The script reads from `scopes-config.yaml` by default. You can specify a different config file:
```bash
go run scopes.go -config=my-config.yaml
```
### Configuration Format
The configuration file uses YAML format with a natural tree structure. The indentation itself represents the hierarchy:
- **scopes**: Map of scope definitions (key is the scope name)
- **tree**: Tree structure of scope nodes where the YAML structure defines parent-child relationships
- **navigations**: Map of scope navigations linking URLs to scopes (key is the navigation name)
Example:
```yaml
scopes:
app1:
title: Application 1
filters:
- key: app
operator: equals
value: app1
tree:
environments:
title: Environments
nodeType: container
children:
production:
title: Production
nodeType: container
children:
app1-prod:
title: Application 1
nodeType: leaf
linkId: app1
linkType: scope
navigations:
# Link to a dashboard
app1-nav:
url: /d/86Js1xRmk
scope: app1
# Link to another dashboard
app2-nav:
url: /d/GlAqcPgmz
scope: app2
# Custom URLs
explore-nav:
url: /explore
scope: app1
```
### Tree Structure
The tree structure uses YAML's natural indentation to represent hierarchy:
- **Key**: Unique identifier for the node (will be prefixed with "gdev-")
- **title**: Display title
- **nodeType**: Either "container" (can have children) or "leaf" (selectable scope)
- **linkId**: References a scope name (if nodeType is "leaf")
- **linkType**: Usually "scope"
- **children**: Map of child nodes (nested structure follows YAML indentation)
### Node Types
- **container**: A category/grouping node that can contain other nodes
- **leaf**: A selectable node that links to a scope
### Navigations
Navigations link URLs to scopes. The `url` field should contain the full URL path (e.g., `/d/abc123` for dashboards or `/explore` for other pages).
To find dashboard UIDs from gdev dashboards:
```bash
# Find UIDs of all gdev dashboards
find devenv/dev-dashboards -name "*.json" -exec sh -c 'echo "{}:" && jq -r ".uid // .dashboard.uid // \"NO_UID\"" {}' \;
# Or for a specific dashboard
jq -r ".uid // .dashboard.uid" devenv/dev-dashboards/all-panels.json
```
## Environment Variables
- `GRAFANA_URL`: Grafana URL (default: http://localhost:3000)
- `GRAFANA_NAMESPACE`: Namespace (default: default)
- `GRAFANA_USER`: Grafana username (default: admin)
- `GRAFANA_PASSWORD`: Grafana password (default: admin)
## Command Line Flags
- `-url`: Grafana URL
- `-namespace`: Namespace
- `-config`: Config file path (default: scopes-config.yaml)
- `-user`: Grafana username
- `-password`: Grafana password
- `-clean`: Delete all gdev-prefixed resources
## Prefix
All resources are automatically prefixed with "gdev-" to avoid conflicts with production data.

View File

@@ -0,0 +1,84 @@
scopes:
app1:
title: Application 1
filters:
- key: app
operator: equals
value: app1
app2:
title: Application 2
filters:
- key: app
operator: equals
value: app2
cluster1:
title: Cluster 1
filters:
- key: cluster
operator: equals
value: cluster1
tree:
gdev-scopes:
title: gdev-scopes
nodeType: container
children:
production:
title: Production
nodeType: container
children:
app1-prod:
title: Application 1
nodeType: leaf
linkId: app1
linkType: scope
app2-prod:
title: Application 2
nodeType: leaf
linkId: app2
linkType: scope
test-cases:
title: Test cases
nodeType: container
disableMultiSelect: true
children:
test-case-1:
title: Test case 1
nodeType: leaf
linkId: test-case-1
linkType: scope
test-case-2:
title: Test case 2
nodeType: leaf
linkId: test-case-2
linkType: scope
clusters:
title: Clusters
nodeType: container
linkId: cluster1
linkType: scope
children:
cluster1-node:
title: Cluster 1
nodeType: leaf
linkId: cluster1
linkType: scope
navigations:
# Example: Link to a dashboard
app1-nav:
url: /d/86Js1xRmk
scope: app1
# Example: Link to a dashboard with full URL (already has /d/)
app2-nav:
url: /d/GlAqcPgmz
scope: app2
# Example: Custom URL path
custom-nav:
url: /explore
scope: app1

433
devenv/scopes/scopes.go Normal file
View File

@@ -0,0 +1,433 @@
//go:build ignore
// +build ignore
package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"net/http"
"os"
"strings"
"gopkg.in/yaml.v3"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/apps/scope/pkg/apis/scope/v0alpha1"
)
const (
prefix = "gdev"
apiVersion = "scope.grafana.app/v0alpha1"
defaultURL = "http://localhost:3000"
defaultUser = "admin"
)
var (
grafanaURL = flag.String("url", getEnv("GRAFANA_URL", defaultURL), "Grafana URL")
namespace = flag.String("namespace", getEnv("GRAFANA_NAMESPACE", "default"), "Namespace")
configFile = flag.String("config", "scopes-config.yaml", "Config file path")
user = flag.String("user", getEnv("GRAFANA_USER", defaultUser), "Grafana username")
password = flag.String("password", getEnv("GRAFANA_PASSWORD", "admin"), "Grafana password")
cleanupFlag = flag.Bool("clean", false, "Delete all gdev-prefixed resources")
)
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}
type Config struct {
Scopes map[string]ScopeConfig `yaml:"scopes"`
Tree map[string]TreeNode `yaml:"tree"`
Navigations map[string]NavigationConfig `yaml:"navigations"`
}
// ScopeConfig is used for YAML parsing - converts to v0alpha1.ScopeSpec
type ScopeConfig struct {
Title string `yaml:"title"`
Filters []ScopeFilterConfig `yaml:"filters"`
}
// ScopeFilterConfig is used for YAML parsing - converts to v0alpha1.ScopeFilter
type ScopeFilterConfig struct {
Key string `yaml:"key"`
Value string `yaml:"value"`
Values []string `yaml:"values,omitempty"`
Operator string `yaml:"operator"`
}
// TreeNode is used for YAML parsing - converts to v0alpha1.ScopeNodeSpec
type TreeNode struct {
Title string `yaml:"title"`
NodeType string `yaml:"nodeType"`
LinkID string `yaml:"linkId,omitempty"`
LinkType string `yaml:"linkType,omitempty"`
Children map[string]TreeNode `yaml:"children,omitempty"`
}
type NavigationConfig struct {
URL string `yaml:"url"` // URL path (e.g., /d/abc123 or /explore)
Scope string `yaml:"scope"`
}
// Helper function to convert ScopeFilterConfig to v0alpha1.ScopeFilter
func convertFilter(cfg ScopeFilterConfig) v0alpha1.ScopeFilter {
filter := v0alpha1.ScopeFilter{
Key: cfg.Key,
Value: cfg.Value,
Values: cfg.Values,
Operator: v0alpha1.FilterOperator(cfg.Operator),
}
return filter
}
// Helper function to convert ScopeConfig to v0alpha1.ScopeSpec
func convertScopeSpec(cfg ScopeConfig) v0alpha1.ScopeSpec {
filters := make([]v0alpha1.ScopeFilter, len(cfg.Filters))
for i, f := range cfg.Filters {
filters[i] = convertFilter(f)
}
return v0alpha1.ScopeSpec{
Title: cfg.Title,
Filters: filters,
}
}
type Client struct {
baseURL string
namespace string
httpClient *http.Client
auth string
}
func NewClient(baseURL, namespace, user, password string) *Client {
return &Client{
baseURL: baseURL,
namespace: namespace,
httpClient: &http.Client{},
auth: basicAuth(user, password),
}
}
func basicAuth(username, password string) string {
return fmt.Sprintf("%s:%s", username, password)
}
func (c *Client) makeRequest(method, endpoint string, body []byte) error {
url := fmt.Sprintf("%s/apis/%s/namespaces/%s%s", c.baseURL, apiVersion, c.namespace, endpoint)
var req *http.Request
var err error
if body != nil {
req, err = http.NewRequest(method, url, bytes.NewBuffer(body))
} else {
req, err = http.NewRequest(method, url, nil)
}
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(strings.Split(c.auth, ":")[0], strings.Split(c.auth, ":")[1])
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
bodyBytes, _ := io.ReadAll(resp.Body)
// For DELETE requests, 404 is acceptable (resource already deleted)
if resp.StatusCode == 404 {
return nil
}
return fmt.Errorf("API request failed: HTTP %d - %s", resp.StatusCode, string(bodyBytes))
}
return nil
}
func (c *Client) createScope(name string, cfg ScopeConfig) error {
prefixedName := prefix + "-" + name
spec := convertScopeSpec(cfg)
resource := v0alpha1.Scope{
TypeMeta: metav1.TypeMeta{
APIVersion: apiVersion,
Kind: "Scope",
},
ObjectMeta: metav1.ObjectMeta{
Name: prefixedName,
},
Spec: spec,
}
body, err := json.Marshal(resource)
if err != nil {
return fmt.Errorf("failed to marshal scope: %w", err)
}
fmt.Printf("✓ Creating scope: %s\n", prefixedName)
return c.makeRequest("POST", "/scopes", body)
}
func (c *Client) createScopeNode(name string, node TreeNode, parentName string) error {
prefixedName := prefix + "-" + name
prefixedParent := ""
prefixedLinkID := ""
if parentName != "" {
prefixedParent = prefix + "-" + parentName
}
if node.LinkID != "" {
prefixedLinkID = prefix + "-" + node.LinkID
}
nodeType := v0alpha1.NodeType(node.NodeType)
if nodeType == "" {
nodeType = v0alpha1.NodeTypeContainer
}
linkType := v0alpha1.LinkType(node.LinkType)
if linkType == "" {
linkType = v0alpha1.LinkTypeScope
}
spec := v0alpha1.ScopeNodeSpec{
Title: node.Title,
NodeType: nodeType,
DisableMultiSelect: false,
}
if prefixedParent != "" {
spec.ParentName = prefixedParent
}
if prefixedLinkID != "" {
spec.LinkID = prefixedLinkID
spec.LinkType = linkType
}
resource := v0alpha1.ScopeNode{
TypeMeta: metav1.TypeMeta{
APIVersion: apiVersion,
Kind: "ScopeNode",
},
ObjectMeta: metav1.ObjectMeta{
Name: prefixedName,
},
Spec: spec,
}
body, err := json.Marshal(resource)
if err != nil {
return fmt.Errorf("failed to marshal scope node: %w", err)
}
fmt.Printf("✓ Creating scope node: %s\n", prefixedName)
return c.makeRequest("POST", "/scopenodes", body)
}
func (c *Client) createScopeNavigation(name string, nav NavigationConfig) error {
prefixedName := prefix + "-" + name
prefixedScope := prefix + "-" + nav.Scope
if nav.URL == "" {
return fmt.Errorf("navigation %s must have 'url' specified", name)
}
spec := v0alpha1.ScopeNavigationSpec{
URL: nav.URL,
Scope: prefixedScope,
}
resource := v0alpha1.ScopeNavigation{
TypeMeta: metav1.TypeMeta{
APIVersion: apiVersion,
Kind: "ScopeNavigation",
},
ObjectMeta: metav1.ObjectMeta{
Name: prefixedName,
},
Spec: spec,
}
body, err := json.Marshal(resource)
if err != nil {
return fmt.Errorf("failed to marshal scope navigation: %w", err)
}
fmt.Printf("✓ Creating scope navigation: %s\n", prefixedName)
return c.makeRequest("POST", "/scopenavigations", body)
}
func (c *Client) createTreeNodes(children map[string]TreeNode, parentName string) error {
for name, node := range children {
// Build full node name by appending to parent name
// This makes it easy to see the tree path from the node name
fullNodeName := name
if parentName != "" {
fullNodeName = parentName + "-" + name
}
// parentName here is the full parent name (already includes full path)
err := c.createScopeNode(fullNodeName, node, parentName)
if err != nil {
return err
}
if len(node.Children) > 0 {
// Pass fullNodeName as parent for children (will be prefixed with "gdev-" in createScopeNode)
if err := c.createTreeNodes(node.Children, fullNodeName); err != nil {
return err
}
}
}
return nil
}
func (c *Client) deleteResources() {
fmt.Println("Deleting all gdev-prefixed resources...")
// Delete scopes (silently handle errors if endpoints aren't available)
c.deleteResourceType("/scopes", "scope")
// Delete scope nodes
c.deleteResourceType("/scopenodes", "scope node")
// Delete scope navigations
c.deleteResourceType("/scopenavigations", "scope navigation")
fmt.Println("✓ Cleanup complete")
}
func (c *Client) deleteResourceType(endpoint, resourceType string) {
url := fmt.Sprintf("%s/apis/%s/namespaces/%s%s", c.baseURL, apiVersion, c.namespace, endpoint)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
// Silently skip if we can't create request
return
}
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(strings.Split(c.auth, ":")[0], strings.Split(c.auth, ":")[1])
resp, err := c.httpClient.Do(req)
if err != nil {
// Silently skip if endpoint isn't available
return
}
defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
// Silently skip if endpoint returns error (might not be available)
return
}
var listResponse struct {
Items []struct {
Metadata struct {
Name string `json:"name"`
} `json:"metadata"`
} `json:"items"`
}
bodyBytes, _ := io.ReadAll(resp.Body)
if err := json.Unmarshal(bodyBytes, &listResponse); err != nil {
// Silently skip if we can't decode response
return
}
if len(listResponse.Items) == 0 {
return
}
deletedCount := 0
for _, item := range listResponse.Items {
if strings.HasPrefix(item.Metadata.Name, prefix+"-") {
fmt.Printf(" Deleting %s: %s\n", resourceType, item.Metadata.Name)
deleteURL := fmt.Sprintf("%s/%s", endpoint, item.Metadata.Name)
if err := c.makeRequest("DELETE", deleteURL, nil); err != nil {
// Silently skip deletion errors
} else {
deletedCount++
}
}
}
}
func main() {
flag.Parse()
client := NewClient(*grafanaURL, *namespace, *user, *password)
if *cleanupFlag {
// Cleanup should be silent if endpoints aren't available
client.deleteResources()
return
}
configData, err := os.ReadFile(*configFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading config file: %v\n", err)
os.Exit(1)
}
var config Config
if err := yaml.Unmarshal(configData, &config); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing config file: %v\n", err)
os.Exit(1)
}
fmt.Printf("Loading configuration from: %s\n", *configFile)
fmt.Printf("Grafana URL: %s\n", *grafanaURL)
fmt.Printf("Namespace: %s\n", *namespace)
fmt.Printf("Prefix: %s\n\n", prefix)
// Create scopes
fmt.Println("Creating scopes...")
for name, scope := range config.Scopes {
if err := client.createScope(name, scope); err != nil {
fmt.Fprintf(os.Stderr, "Error creating scope %s: %v\n", name, err)
os.Exit(1)
}
}
fmt.Println()
// Create scope nodes (tree structure)
if len(config.Tree) > 0 {
fmt.Println("Creating scope nodes...")
if err := client.createTreeNodes(config.Tree, ""); err != nil {
fmt.Fprintf(os.Stderr, "Error creating scope nodes: %v\n", err)
os.Exit(1)
}
fmt.Println()
}
// Create scope navigations
if len(config.Navigations) > 0 {
fmt.Println("Creating scope navigations...")
for name, nav := range config.Navigations {
if err := client.createScopeNavigation(name, nav); err != nil {
fmt.Fprintf(os.Stderr, "Error creating scope navigation %s: %v\n", name, err)
os.Exit(1)
}
}
fmt.Println()
}
fmt.Println("✓ All resources created successfully!")
}

View File

@@ -19,6 +19,13 @@ bulkFolders() {
ln -s -f ../../../devenv/bulk-folders/bulk-folders.yaml ../conf/provisioning/dashboards/bulk-folders.yaml
}
scopes() {
echo -e "\xE2\x9C\x94 Setting up scopes, scope nodes, and scope navigations"
cd scopes
go run scopes.go
cd ..
}
requiresJsonnet() {
if ! type "jsonnet" > /dev/null; then
echo "you need you install jsonnet to run this script"
@@ -49,6 +56,12 @@ undev() {
rm -rf bulk-folders/Bulk\ Folder*
echo -e " \xE2\x9C\x94 Reverting bulk-folders provisioning"
# Removing scopes, scope nodes, and scope navigations
cd scopes
go run scopes.go -clean
cd ..
echo -e " \xE2\x9C\x94 Deleting scopes, scope nodes, and scope navigations"
# Removing the symlinks
rm -f ../conf/provisioning/dashboards/custom.yaml
rm -f ../conf/provisioning/dashboards/bulk-folders.yaml
@@ -63,6 +76,7 @@ usage() {
echo " bulk-dashboards - provision 400 dashboards"
echo " bulk-folders [folders] [dashboards] - provision many folders with dashboards"
echo " bulk-folders - provision 200 folders with 3 dashboards in each"
echo " scopes - provision scopes, scope nodes, and scope navigations"
echo " no args - provision core datasources and dev dashboards"
echo " undev - removes any provisioning done by the setup.sh"
}
@@ -80,6 +94,8 @@ main() {
bulkDashboard
elif [[ $cmd == "bulk-folders" ]]; then
bulkFolders "$arg1"
elif [[ $cmd == "scopes" ]]; then
scopes
elif [[ $cmd == "undev" ]]; then
undev
else

View File

@@ -53,11 +53,7 @@ The following applies:
## Enable the Frontend Sandbox
The Frontend Sandbox feature is currently behind the `pluginsFrontendSandbox` feature flag. To enable it, you need to:
1. Enable the feature flag in your Grafana configuration. For more information about enabling feature flags, refer to [Configure feature toggles](/docs/grafana/<GRAFANA_VERSION>/setup-grafana/configure-grafana/feature-toggles/).
2. For self-hosted Grafana installations, add the plugin IDs you want to sandbox in the `security` section using the `enable_frontend_sandbox_for_plugins` configuration option.
For self-hosted Grafana installations, add the plugin IDs you want to sandbox in the `security` section using the `enable_frontend_sandbox_for_plugins` configuration option.
For Grafana Cloud users, you can simply use the toggle switch in the plugin catalog page to enable or disable the sandbox for each plugin. By default, the sandbox is disabled for all plugins.

View File

@@ -68,7 +68,21 @@ You can change this behavior by disabling the `alertingSaveStateCompressed` feat
You can also reduce database load by writing states periodically instead of after every evaluation.
To save state periodically:
There are two approaches for periodic state saving:
#### Compressed periodic saves
You can combine compressed alert state storage with periodic saves by enabling both `alertingSaveStateCompressed` and `alertingSaveStatePeriodic` feature toggles together.
This approach groups all alert instances by rule UID and compresses them together for efficient storage.
When both feature toggles are enabled, Grafana will save compressed alert states at the interval specified by `state_periodic_save_interval`. Note that in compressed mode, the `state_periodic_save_batch_size` setting is ignored as the system groups instances by rule UID rather than by batch size.
#### Batch-based periodic saves
Alternatively, you can use batch-based periodic saves without compression:
This approach processes individual alert instances in batches of a specified size.
1. Enable the `alertingSaveStatePeriodic` feature toggle.
1. Disable the `alertingSaveStateCompressed` feature toggle.
@@ -77,7 +91,7 @@ By default, it saves the states every 5 minutes to the database and on each shut
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
##### Jitter for batch-based 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.

View File

@@ -250,6 +250,19 @@ You can query CloudWatch Logs using three supported query language options:
1. Select a region.
1. Select **CloudWatch Logs** from the query type drop-down.
1. Select the Logs Mode depending on whether you would like to query CloudWatch Logs Insights or Log Anomalies
**Log Anomalies**
[Anomaly detection](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/LogsAnomalyDetection.html) uses machine-learning and pattern recognition to establish baselines of typical log content.
The Log Anomalies query editor fetches the list of anomalies detected in your CloudWatch service. In order to query log anomalies in the editor, a log anomaly detector must be created in the AWS CloudWatch console first.
The log trend cell shows the number of occurrences of the pattern over the selected query time range.
The table shows 50 log anomalies at a time. If you would like to narrow down the list, you can filter anomalies by their ARN and suppressed state.
In addition to this, you can use the Logs Insights QL editor and the `anomaly` command together with the `patterns` command to define and display log anomalies in real time. See the [CloudWatch Logs Insights](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/LogsAnomalyDetection-Insights.html) documentation for more info.
**Logs Insights**
1. Select the query language you would like to use in the **Query Language** drop-down.
1. Click **Select log groups** and choose up to 20 log groups to query.
1. Use the main input area to write your logs query. Amazon CloudWatch only supports a subset of OpenSearch SQL and PPL commands. To find out more about the syntax supported, consult [Amazon CloudWatch Logs documentation](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_AnalyzeLogData_Languages.html)
@@ -258,7 +271,7 @@ You can query CloudWatch Logs using three supported query language options:
You must specify the region and log groups when querying with **Logs Insights QL** and **OpenSearch PPL**. **OpenSearch SQL** doesn't require log group selection. However, selecting log groups simplifies query writing by populating syntax suggestions with discovered log group fields.
{{< /admonition >}}
Click **CloudWatch Logs Insights** to interactively view, search, and analyze your log data in the CloudWatch Logs Insights console. If you're not logged in to the CloudWatch console, the link forwards you to the login page.
Click **View in CloudWatch console** to interactively view, search, and analyze your log data in the CloudWatch Logs Insights console. If you're not logged in to the CloudWatch console, the link forwards you to the login page.
### Query Log groups with OpenSearch SQL

View File

@@ -0,0 +1,154 @@
---
aliases:
labels:
products:
- cloud
- enterprise
- oss
menuTitle: Concepts
title: Data sources, plugins and integrations
weight: 70
refs:
data-source-management:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/
- pattern: /docs/grafana-cloud/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/data-source-management/
plugin-management:
- pattern: /docs/grafana/
destination: /docs/grafana/<GRAFANA_VERSION>/administration/plugin-management/
- pattern: /docs/grafana-cloud
destination: /docs/grafana/<GRAFANA_VERSION>/administration/plugin-management/
---
# Data sources, plugins, and integrations
When working with Grafana, you'll encounter three key concepts: data sources, plugins, and integrations. Each one is essential in building effective monitoring solutions, but they serve distinct purposes, and are often confused with one another. This document clarifies the meaning of each concept and what each one does, when to use it, and how they work together to create observability solutions in Grafana.
## Data sources
A data source is a connection to a specific database, monitoring system, service, or other external location that stores data, metrics, logs, or traces. Examples include Prometheus, InfluxDB, PostgreSQL, or CloudWatch. When you configure a data source in Grafana, you're telling it where to fetch data from, providing connection details, credentials, and endpoints. Data sources are the foundation for working with Grafana. Without them, Grafana has nothing to visualize. Once configured, you can query your Prometheus data source to display CPU metrics, or query CloudWatch to visualize AWS infrastructure performance.
## Plugins
A plugin extends Grafanas core functionality. Plugins can add new data source types, visualization panels, or full-featured applications that integrate with Grafana. They make Grafana modular and extensible.
Plugins come in three types:
- **Data source plugins** connect Grafana to **external data sources**. You use this type of plugin when you want to access and work with data from an external source or third party. Examples include Prometheus, MSSQL, and Databricks.
- **Panel plugins** control how data appears in Grafana dashboards. Examples of panel plugins include pie chart, candlestick, and traffic light. Note that in some cases, panels don't rely on a data source at all. The **Text** panel can render static or templated content without querying data. Panels can also support user-driven actions. For example, the **Button** panel can trigger workflows or external calls.
- **App plugins** allow you to bundle data sources and panel plugins within a single package. They enable you to create custom pages within Grafana that can function like dashboards, providing dedicated spaces for documentation, sign-up forms, custom UI extensions, and integration with other services via HTTP. Cloud apps built as app plugins offer out-of-the-box observability solutions, such as Azure Cloud Native Monitoring and Redis Application, that provide comprehensive monitoring capabilities compared to standalone integrations
## Integrations
_Integrations are exclusive to Grafana Cloud._ An integration is a pre-packaged monitoring solution that bundles export/scrape configurations, pre-built dashboards, alert rules, and sometimes recording rules. Unlike standalone data sources, integrations handle the complete workflow: they configure how telemetry is collected and sent to Grafana Cloud's hosted databases, then provide ready-to-use dashboards and alerts. For example, a Kubernetes integration configures metric collection from your cluster, creates dashboards for monitoring, and sets up common alerts—all working together out of the box
## When to use each
Use a data source when:
- You want to connect Grafana to a specific system (for example, Prometheus or MySQL).
- Youre building custom dashboards with hand-picked metrics and visualizations.
- Your monitoring needs are unique or not covered by pre-packaged integrations.
Use a plugin when:
- You need to connect to a system Grafana doesnt support natively.
- You want to add new functionality (visualizations, workflows, or app-style extensions).
- You have specialized or industry-specific requirements (for example, IoT).
Use an integration when:
- Youre using Grafana Cloud and want a quick, pre-built setup.
- You prefer minimal configuration with ready-to-use dashboards and alerts.
- Youre new to observability and want to learn what good monitoring looks like.
## Relationships and interactions
How data sources, plugins, and integrations work together:
- Plugins extend what Grafana can do.
- Data sources define where Grafana reads data from.
- Integrations combine telemetry collection and pre-built content to create complete monitoring solutions.
Examples:
- Install the Databricks data source plugin. Configure the Databricks data source and run SQL queries against your Databricks workspace. Use the `Histogram` panel to visualize distributions in your query results, such as latency buckets, job durations, or model output scores.
- Install the Redis Application app plugin. This app provides a unified experience for monitoring Redis by working with your existing Redis data source. It adds custom pages for configuration and exploration, along with prebuilt dashboards, commands, and visualizations that help you analyze performance, memory usage, and key activity.
<!-- - Install the Azure Cloud Native Monitoring app plugin, which bundles the app and data source plugin types. It includes data source plugins for Azure Monitor and Log Analytics, panel plugins for visualizing Azure metrics, and a custom configuration page for managing authentication and subscriptions. -->
- If youre using Grafana Cloud, add the ClickHouse integration. This integration provides pre-built dashboards and alerts to monitor ClickHouse cluster metrics and logs, enabling users to visualize and analyze their ClickHouse performance and health in real-time.
## Frequently asked questions
**What's the difference between a data source and a data source plugin?**
A data source plugin is a **software component that enables Grafana to communicate** with specific types of databases or services, like Prometheus, MySQL, or InfluxDB. A data source is **an actual configured connection** to one of these databases, including the credentials, URL, and settings needed to retrieve data.
Think of it this way: You _install_ a plugin but _configure_ a data source.
**Do I need a plugin to use a data source?**
You must install the plugin before you configure or use the data source. Each data source plugin has its own versioning and lifecycle. Grafana includes built-in core data sources, which can be thought of as pre-installed plugins.
**Can I use integrations in self-hosted Grafana?**
No, integrations are exclusive to Grafana Cloud. In self-hosted Grafana, you can replicate similar setups manually using data sources and dashboards.
**Aren't integrations just pre-built dashboards?**
No, integrations are much more than just dashboards. While dashboards are part of an integration, theyre only one piece. Integrations typically include:
- Data collection setup (for example, pre-configured agents or exporters).
- Predefined metrics and queries tailored to the technology.
- Alerting rules and notifications to help detect common issues.
- Dashboards to visualize and explore that data.
**Whats the difference between plugin types?**
A data source plugin in Grafana is a software component that enables Grafana to connect to and retrieve data from various external data sources. After you install the plugin, you can use it to configure one or more data sources. Each data source defines the actual connection details, like the server URL, authentication method, and query options.
A panel plugin in Grafana is an extension that allows you to add new and custom visualizations to your Grafana dashboards. While Grafana comes with several built-in panel types (like graphs, single stats, and tables), panel plugins extend this functionality by providing specialized ways to display data.
An app plugin in Grafana is a type of plugin that provides a comprehensive, integrated, and often out-of-the-box experience within Grafana. Unlike data source plugins, which connect to external data sources, or panel plugins, which provide new visualization types, app plugins can combine various functionalities to create a more complete experience.
**How do data sources and integrations differ in how they handle data?**
Data sources query data where it already lives. They connect Grafana to an external system or database, such as Prometheus, MySQL, or Elasticsearch and fetch data on demand. You keep full control over your own data stores, schemas and retention policies.
In contrast, integrations focus on getting data into Grafana Clouds hosted backends. They ingest metrics, logs, and traces into systems like Mimir, Loki, or Tempo, using pre-configured agents and pipelines. Instead of querying an external database, Grafana queries its own managed storage where the integration has placed the data.
## Summary reference
Use the following table to compare how data sources, plugins, and integrations differ in scope, purpose, and use. It highlights where each applies within Grafana, what problems it solves, and how they work together to build observability solutions.
| Concept | Where it applies | Purpose | What it includes | When to use it | Example |
| ---------------------- | ---------------------- | ---------------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------- | ------------------------------------------ |
| **Data source** | Self-hosted and Cloud | Connect to external metrics, logs, or traces storage | Connection settings, auth, query config | Visualize data from a database or monitoring system | Prometheus, CloudWatch, PostgreSQL |
| **Plugin** | Self-hosted and Cloud | Extend Grafana with new capabilities | Three types: data source, panel, and app | Add connectivity or functionality not included by default | Plotly panel, MongoDB data source |
| **App plugin** | Self-hosted and Cloud | Bundle plugins with custom pages or UI | Data source + panel plugins + custom routes | Create a dedicated app-like experience | Azure Cloud Native Monitoring |
| **Panel plugin** | Self-hosted and Cloud | Add new visualization types | Custom panels and visualization logic | Display data beyond built-in visualizations | Pie chart, Candlestick, Geomap |
| **Data source plugin** | Self-hosted and Cloud | Connect to a new external system type | Connector code for querying that system | Access data from an unsupported backend | Databricks, MongoDB, MSSQL |
| **Integration** | Grafana Cloud only | Pre-packaged observability for a specific technology | Telemetry config, dashboards, alerts, recording rules | Get an out-of-the-box setup with minimal configuration | Kubernetes, Redis, NGINX |
For detailed documentation and how-to guides related to data sources, plugins, and integrations, refer to the following references:
**Data sources**:
- [Manage data sources](ref:data-source-management)
**Plugins**:
- [Plugin types and usage](https://grafana.com/developers/plugin-tools/key-concepts/plugin-types-usage)
- [App plugins](https://grafana.com/developers/plugin-tools/how-to-guides/app-plugins/)
- [Data source plugins](https://grafana.com/developers/plugin-tools/how-to-guides/data-source-plugins/)
- [Panel plugins](https://grafana.com/developers/plugin-tools/how-to-guides/panel-plugins/)
**Integrations**:
- [Grafana integrations](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/integrations/)
- [Install and manage integrations](https://grafana.com/docs/grafana-cloud/monitor-infrastructure/integrations/install-and-manage-integrations/)

View File

@@ -18,7 +18,7 @@ weight: 300
# Manage resources with Grafana CLI
{{< admonition type="note" >}}
`grafanactl` is under active development. Command-line flags and subcommands described here may change. This document outlines the target workflows the tool is expected to support.
`grafanactl` is under active development. Command-line flags and subcommands described here may change. This document outlines the target workflows the tool is expected to support. You can find a full list of supported commands [in this page](https://grafana.github.io/grafanactl/reference/cli/grafanactl/).
{{< /admonition >}}
## Migrate resources between environments

View File

@@ -1969,6 +1969,12 @@ If a rule frequency is lower than this value, then this value is enforced.
<hr>
#### `rule_version_record_limit`
Defines the limits for how many alert rule versions are stored in the database per alert rule.
The default `0` value means there's no limit.
### `[unified_alerting.screenshots]`
For more information about screenshots, refer to [Images in notifications](../../alerting/configure-notifications/template-notifications/images-in-notifications/).

View File

@@ -40,7 +40,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general-
| `transformationsRedesign` | Enables the transformations redesign | Yes |
| `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes |
| `dashgpt` | Enable AI powered features in dashboards | Yes |
| `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes |
| `formatString` | Enable format string transformer | Yes |
| `kubernetesDashboards` | Use the kubernetes API in the frontend for dashboards | Yes |
| `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | Yes |

View File

@@ -1,16 +1,9 @@
import { Page, Locator } from '@playwright/test';
import { test, expect } from '@grafana/plugin-e2e';
import testDashboard from '../dashboards/AdHocFilterTest.json';
import { getCell } from '../panels-suite/table-utils';
// Helper function to get a specific cell in a table
const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: number) =>
loc
.getByRole('row')
.nth(rowIdx)
.getByRole(rowIdx === 0 ? 'columnheader' : 'gridcell')
.nth(colIdx);
const fixture = require('../fixtures/prometheus-response.json');
test.describe(
'Dashboard with Table powered by Prometheus data source',
@@ -46,80 +39,90 @@ test.describe(
gotoDashboardPage,
selectors,
}) => {
// Handle query and query_range API calls
// Handle query and query_range API calls. Ideally, this would instead be directly tested against gdev-prometheus.
await page.route(/\/api\/ds\/query/, async (route) => {
const fixture = require('../fixtures/prometheus-response.json');
// during the test, we select the "inner_eval" slice to filter; this simulates the behavior
// of prometheus applying that filter and removing dataframes from the response.
if (route.request().postData()?.includes('{slice=\\\"inner_eval\\\"}')) {
delete fixture.results.A.frames[1];
const response = JSON.parse(JSON.stringify(fixture));
// This simulates the behavior of prometheus applying a filter and removing dataframes from the response where
// the label matches the selected filter. We check for either the slice being applied inline into the prometheus
// query or the adhoc filter being present in the request body of prometheus applying that filter and removing
// dataframes from the response.
const postData = route.request().postData();
const match =
postData?.match(/{slice=\\\"([\w_]+)\\\"}/) ??
postData?.match(/"adhocFilters":\[{"key":"slice","operator":"equals","value":"([\w_]+)"}\]/);
if (match) {
response.results.A.frames = response.results.A.frames.filter((frame) =>
frame.schema.fields.every((field) => !field.labels || field.labels.slice === match[1])
);
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(fixture),
body: JSON.stringify(response),
});
});
const dashboardPage = await gotoDashboardPage({ uid: dashboardUID });
const panel = dashboardPage.getByGrafanaSelector(
let panel = dashboardPage.getByGrafanaSelector(
selectors.components.Panels.Panel.title('Table powered by Prometheus')
);
await expect(panel).toBeVisible();
await expect(panel, 'panel is rendered').toBeVisible();
// Wait for the table to load completely
await expect(panel.locator('.rdg')).toBeVisible();
const table = panel.locator('.rdg');
await expect(table, 'table is rendered').toBeVisible();
// Get the first data cell in the third column (row 1, column 2)
const labelValueCell = await getCell(panel, 1, 1);
await expect(labelValueCell).toBeVisible();
const firstValue = (await getCell(table, 1, 1).textContent())!;
const secondValue = (await getCell(table, 2, 1).textContent())!;
expect(firstValue, `first cell is "${firstValue}"`).toBeTruthy();
expect(secondValue, `second cell is "${secondValue}"`).toBeTruthy();
expect(firstValue, 'first and second cell values are different').not.toBe(secondValue);
// Get the cell value before clicking the filter button
const labelValue = await labelValueCell.textContent();
expect(labelValue).toBeTruthy();
async function performTest(labelValue: string) {
// Confirm both cells are rendered before we proceed
const otherValue = labelValue === firstValue ? secondValue : firstValue;
await expect(table.getByText(labelValue), `"${labelValue}" is rendered`).toContainText(labelValue);
await expect(table.getByText(otherValue), `"${otherValue}" is rendered`).toContainText(otherValue);
const otherValueCell = await getCell(panel, 2, 1);
const otherValueLabel = await otherValueCell.textContent();
expect(otherValueLabel).toBeTruthy();
expect(otherValueLabel).not.toBe(labelValue);
// click the "Filter for value" button on the cell with the specified labelValue
await table.getByText(labelValue).hover();
table.getByText(labelValue).getByRole('button', { name: 'Filter for value' }).click();
// Hover over the first cell to trigger the appearance of filter actions
await labelValueCell.hover();
// Look for submenu items that contain the filtered value
// The adhoc filter should appear as a filter chip or within the variable controls
const submenuItems = dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.SubMenu.submenuItem);
await expect(submenuItems.filter({ hasText: labelValue }), `submenu contains "${labelValue}"`).toBeVisible();
await expect(
submenuItems.filter({ hasText: otherValue }),
`submenu does not contain "${otherValue}"`
).toBeHidden();
// Check if the "Filter for value" button appears on hover
const filterForValueButton = labelValueCell.getByRole('button', { name: 'Filter for value' });
await expect(filterForValueButton).toBeVisible();
// The URL parameter should contain the filter in format like: var-PromAdHoc=["columnName","=","value"]
const currentUrl = page.url();
const urlParams = new URLSearchParams(new URL(currentUrl).search);
const promAdHocParam = urlParams.get('var-PromAdHoc');
expect(promAdHocParam, `url contains "${labelValue}"`).toContain(labelValue);
expect(promAdHocParam, `url does not contain "${otherValue}"`).not.toContain(otherValue);
// Click on the "Filter for value" button
await filterForValueButton.click();
// finally, let's check that the table was updated and that the value was filtered out when the query was re-run
await expect(table.getByText(labelValue), `"${labelValue}" is still visible`).toHaveText(labelValue);
await expect(table.getByText(otherValue), `"${otherValue}" is filtered out`).toBeHidden();
// Check if the adhoc filter appears in the dashboard submenu
const submenuItems = dashboardPage.getByGrafanaSelector(selectors.pages.Dashboard.SubMenu.submenuItem);
await expect(submenuItems.first()).toBeVisible();
// Remove the adhoc filter by clicking the submenu item again
const filterChip = submenuItems.filter({ hasText: labelValue });
await filterChip.getByLabel(/Remove filter with key/).click();
await page.click('body', { position: { x: 0, y: 0 } }); // click outside to close the open menu from ad-hoc filters
// Look for submenu items that contain the filtered value
// The adhoc filter should appear as a filter chip or within the variable controls
const hasFilterValue = await submenuItems.filter({ hasText: labelValue! }).count();
expect(hasFilterValue).toBeGreaterThan(0);
// the "first" and "second" cells locators don't work here for some reason.
await expect(table.getByText(labelValue), `"${labelValue}" is still rendered`).toContainText(labelValue);
await expect(table.getByText(otherValue), `"${otherValue}" is rendered again`).toContainText(otherValue);
}
const hasOtherValue = await submenuItems.filter({ hasText: otherValueLabel! }).count();
expect(hasOtherValue).toBe(0);
// Check if the URL contains the var-PromAdHoc parameter with the filtered value
const currentUrl = page.url();
expect(currentUrl).toContain('var-PromAdHoc');
// The URL parameter should contain the filter in format like: var-PromAdHoc=["columnName","=","value"]
const urlParams = new URLSearchParams(new URL(currentUrl).search);
const promAdHocParam = urlParams.get('var-PromAdHoc');
expect(promAdHocParam).toBeTruthy();
expect(promAdHocParam).toContain(labelValue!);
expect(promAdHocParam).not.toContain(otherValueLabel!);
// finally, let's check that the table was updated and that the value was filtered out when the query was re-run
await expect(otherValueCell).toBeHidden();
await performTest(firstValue);
await performTest(secondValue);
});
}
);

View File

@@ -17,7 +17,7 @@ test.describe(
tag: ['@dashboards'],
},
() => {
test.fixme('Tests dashboard time zone scenarios', async ({ page, gotoDashboardPage, selectors }) => {
test('Tests dashboard time zone scenarios', async ({ page, gotoDashboardPage, selectors }) => {
const dashboardPage = await gotoDashboardPage({ uid: TIMEZONE_DASHBOARD_UID });
const fromTimeZone = 'UTC';
@@ -106,12 +106,18 @@ test.describe(
zone: 'Browser',
});
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
const relativeTimeRow = dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
.first();
const timezoneRow = dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel in timezone'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
.first();
await expect(relativeTimeRow).toBeVisible();
// Today so far, still in Browser timezone
await setTimeRange(page, dashboardPage, selectors, {
@@ -119,19 +125,8 @@ test.describe(
to: 'now',
});
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel in timezone'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
await expect(relativeTimeRow).toBeVisible();
await expect(timezoneRow).toBeVisible();
// Test UTC timezone
await setTimeRange(page, dashboardPage, selectors, {
@@ -140,12 +135,7 @@ test.describe(
zone: 'Coordinated Universal Time',
});
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
await expect(relativeTimeRow).toBeVisible();
// Today so far, still in UTC timezone
await setTimeRange(page, dashboardPage, selectors, {
@@ -153,19 +143,8 @@ test.describe(
to: 'now',
});
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel in timezone'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
await expect(relativeTimeRow).toBeVisible();
await expect(timezoneRow).toBeVisible();
// Test Tokyo timezone
await setTimeRange(page, dashboardPage, selectors, {
@@ -174,12 +153,7 @@ test.describe(
zone: 'Asia/Tokyo',
});
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
await expect(relativeTimeRow).toBeVisible();
// Today so far, still in Tokyo timezone
await setTimeRange(page, dashboardPage, selectors, {
@@ -187,19 +161,8 @@ test.describe(
to: 'now',
});
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel in timezone'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
await expect(relativeTimeRow).toBeVisible();
await expect(timezoneRow).toBeVisible();
// Test LA timezone
await setTimeRange(page, dashboardPage, selectors, {
@@ -208,12 +171,7 @@ test.describe(
zone: 'America/Los Angeles',
});
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
await expect(relativeTimeRow).toBeVisible();
// Today so far, still in LA timezone
await setTimeRange(page, dashboardPage, selectors, {
@@ -221,19 +179,8 @@ test.describe(
to: 'now',
});
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel with relative time override'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
await expect(
dashboardPage
.getByGrafanaSelector(selectors.components.Panels.Panel.title('Panel in timezone'))
.locator('[role="row"]')
.filter({ hasText: '00:00:00' })
).toBeVisible();
await expect(relativeTimeRow).toBeVisible();
await expect(timezoneRow).toBeVisible();
});
}
);

View File

@@ -22,133 +22,64 @@ test.describe(
tag: ['@panels'],
},
() => {
test.describe('Sandbox disabled', () => {
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
window.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0');
});
await page.reload();
test('Does not add iframes to body', async ({ page, gotoDashboardPage }) => {
await gotoDashboardPage({
uid: DASHBOARD_ID,
});
test('Add iframes to body', async ({ page, gotoDashboardPage }) => {
await gotoDashboardPage({
uid: DASHBOARD_ID,
});
// this button adds 3 iframes to the body
await page.locator('[data-testid="button-create-iframes"]').click();
// this button adds iframes to the body
await page.locator('[data-testid="button-create-iframes"]').click();
const iframeIds = [
'createElementIframe',
'innerHTMLIframe',
'appendIframe',
'prependIframe',
'afterIframe',
'beforeIframe',
'outerHTMLIframe',
'parseFromStringIframe',
'insertBeforeIframe',
'replaceChildIframe',
];
const iframeIds = [
'createElementIframe',
'innerHTMLIframe',
'appendIframe',
'prependIframe',
'afterIframe',
'beforeIframe',
'outerHTMLIframe',
'parseFromStringIframe',
'insertBeforeIframe',
'replaceChildIframe',
];
for (const id of iframeIds) {
await expect(page.locator(`#${id}`)).toBeVisible();
}
});
test('Reaches out of panel div', async ({ page, gotoDashboardPage }) => {
await gotoDashboardPage({
uid: DASHBOARD_ID,
});
// this button reaches out of the panel div and modifies the element dataset
await page.locator('[data-testid="button-reach-out"]').click();
await expect(page.locator('[data-sandbox-test="true"]')).toBeVisible();
});
test('Reaches out of the panel editor', async ({ gotoDashboardPage, page }) => {
await gotoDashboardPage({
uid: DASHBOARD_ID,
queryParams: new URLSearchParams({ editPanel: '1' }),
});
const input = page.locator('[data-testid="panel-editor-custom-editor-input"]');
await expect(input).toBeEnabled();
await expect(input).toHaveValue('');
await input.fill('x');
await expect(input).toHaveValue('x');
await expect(page.locator('[data-sandbox-test="panel-editor"]')).toBeVisible();
});
for (const id of iframeIds) {
await expect(page.locator(`#${id}`)).toBeHidden();
}
});
test.describe('Sandbox enabled', () => {
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
window.localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1');
});
await page.reload();
test('Does not reaches out of panel div', async ({ page, gotoDashboardPage }) => {
await gotoDashboardPage({
uid: DASHBOARD_ID,
});
test('Does not add iframes to body', async ({ page, gotoDashboardPage }) => {
await gotoDashboardPage({
uid: DASHBOARD_ID,
});
// this button reaches out of the panel div and modifies the element dataset
await page.locator('[data-testid="button-reach-out"]').click();
await expect(page.locator('[data-sandbox-test="true"]')).toBeHidden();
});
// this button adds 3 iframes to the body
await page.locator('[data-testid="button-create-iframes"]').click();
const iframeIds = [
'createElementIframe',
'innerHTMLIframe',
'appendIframe',
'prependIframe',
'afterIframe',
'beforeIframe',
'outerHTMLIframe',
'parseFromStringIframe',
'insertBeforeIframe',
'replaceChildIframe',
];
for (const id of iframeIds) {
await expect(page.locator(`#${id}`)).toBeHidden();
}
test('Does not Reaches out of the panel editor', async ({ gotoDashboardPage, page }) => {
await gotoDashboardPage({
uid: DASHBOARD_ID,
queryParams: new URLSearchParams({ editPanel: '1' }),
});
test('Does not reaches out of panel div', async ({ page, gotoDashboardPage }) => {
await gotoDashboardPage({
uid: DASHBOARD_ID,
});
const input = page.locator('[data-testid="panel-editor-custom-editor-input"]');
await expect(input).toBeEnabled();
// this button reaches out of the panel div and modifies the element dataset
await page.locator('[data-testid="button-reach-out"]').click();
await expect(page.locator('[data-sandbox-test="true"]')).toBeHidden();
await input.fill('x');
await expect(page.locator('[data-sandbox-test="panel-editor"]')).toBeHidden();
});
test('Can access specific window global variables', async ({ page, gotoDashboardPage }) => {
await gotoDashboardPage({
uid: DASHBOARD_ID,
});
test('Does not Reaches out of the panel editor', async ({ gotoDashboardPage, page }) => {
await gotoDashboardPage({
uid: DASHBOARD_ID,
queryParams: new URLSearchParams({ editPanel: '1' }),
});
const input = page.locator('[data-testid="panel-editor-custom-editor-input"]');
await expect(input).toBeEnabled();
await input.fill('x');
await expect(page.locator('[data-sandbox-test="panel-editor"]')).toBeHidden();
});
test('Can access specific window global variables', async ({ page, gotoDashboardPage }) => {
await gotoDashboardPage({
uid: DASHBOARD_ID,
});
await page.locator('[data-testid="button-test-globals"]').click();
await expect(page.locator('[data-sandbox-global="Prism"]')).toBeVisible();
await expect(page.locator('[data-sandbox-global="jQuery"]')).toBeVisible();
await expect(page.locator('[data-sandbox-global="location"]')).toBeVisible();
});
await page.locator('[data-testid="button-test-globals"]').click();
await expect(page.locator('[data-sandbox-global="Prism"]')).toBeVisible();
await expect(page.locator('[data-sandbox-global="jQuery"]')).toBeVisible();
await expect(page.locator('[data-sandbox-global="location"]')).toBeVisible();
});
}
);

View File

@@ -65,11 +65,11 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100);
// test that hover overflow works.
const loremIpsumCell = await getCell(page, 1, longTextColIdx);
const loremIpsumCell = getCell(page, 1, longTextColIdx);
await loremIpsumCell.scrollIntoViewIfNeeded();
await loremIpsumCell.hover();
await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeGreaterThan(100);
await (await getCell(page, 1, longTextColIdx + 1)).hover();
await getCell(page, 1, longTextColIdx + 1).hover();
await expect(getCellHeight(page, 1, longTextColIdx)).resolves.toBeLessThan(100);
// enable cell inspect, confirm that hover no longer triggers.
@@ -140,15 +140,15 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
).toBeVisible();
// click the "State" column header to sort it.
const stateColumnHeader = await getCell(page, 0, 1);
const stateColumnHeader = getCell(page, 0, 1);
await stateColumnHeader.getByText('Info').click();
await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'ascending');
expect(getCell(page, 1, 1)).resolves.toContainText('down'); // down or down fast
await expect(getCell(page, 1, 1)).toContainText('down'); // down or down fast
await stateColumnHeader.getByText('Info').click();
await expect(stateColumnHeader).toHaveAttribute('aria-sort', 'descending');
expect(getCell(page, 1, 1)).resolves.toContainText('up'); // up or up fast
await expect(getCell(page, 1, 1)).toContainText('up'); // up or up fast
await stateColumnHeader.getByText('Info').click();
await expect(stateColumnHeader).not.toHaveAttribute('aria-sort');
@@ -171,7 +171,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
const stateColumnHeader = page.getByRole('columnheader').nth(infoColumnIdx);
// get the first value in the "State" column, filter it out, then check that it went away.
const firstStateValue = (await (await getCell(page, 1, infoColumnIdx)).textContent())!;
const firstStateValue = (await getCell(page, 1, infoColumnIdx).textContent())!;
await stateColumnHeader.getByTestId(selectors.components.Panels.Visualization.TableNG.Filters.HeaderButton).click();
const filterContainer = dashboardPage.getByGrafanaSelector(
selectors.components.Panels.Visualization.TableNG.Filters.Container
@@ -188,7 +188,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
await expect(filterContainer).not.toBeVisible();
// did it actually filter out our value?
await expect(getCell(page, 1, infoColumnIdx)).resolves.not.toHaveText(firstStateValue);
await expect(getCell(page, 1, infoColumnIdx)).not.toHaveText(firstStateValue);
});
test('Tests pagination, row height adjustment', async ({ gotoDashboardPage, selectors, page }) => {
@@ -289,7 +289,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
const dataLinkColIdx = await getColumnIdx(page, 'Data Link');
// Info column has a single DataLink by default.
const infoCell = await getCell(page, 1, infoColumnIdx);
const infoCell = getCell(page, 1, infoColumnIdx);
await expect(infoCell.locator('a')).toBeVisible();
expect(infoCell.locator('a')).toHaveAttribute('href');
expect(infoCell.locator('a')).not.toHaveAttribute('aria-haspopup');
@@ -306,7 +306,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
continue;
}
const cell = await getCell(page, 1, colIdx);
const cell = getCell(page, 1, colIdx);
await expect(cell.locator('a')).toBeVisible();
expect(cell.locator('a')).toHaveAttribute('href');
expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu');
@@ -319,7 +319,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
// loop thru the columns, click the links, observe that the tooltip appears, and close the tooltip.
for (let colIdx = 0; colIdx < colCount; colIdx++) {
const cell = await getCell(page, 1, colIdx);
const cell = getCell(page, 1, colIdx);
if (colIdx === infoColumnIdx) {
// the Info column should still have its single link.
expect(cell.locator('a')).not.toHaveAttribute('aria-haspopup', 'menu');
@@ -433,7 +433,7 @@ test.describe('Panels test: Table - Kitchen Sink', { tag: ['@panels', '@table']
await filterContainer.getByTitle('up', { exact: true }).locator('label').click();
await filterContainer.getByRole('button', { name: 'Ok' }).click();
const cell = await getCell(page, 1, dataLinkColumnIdx);
const cell = getCell(page, 1, dataLinkColumnIdx);
await expect(cell).toBeVisible();
await expect(cell).toHaveCSS('text-decoration', /line-through/);

View File

@@ -1,6 +1,6 @@
import { Page, Locator } from '@playwright/test';
export const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: number) =>
export const getCell = (loc: Page | Locator, rowIdx: number, colIdx: number) =>
loc
.getByRole('row')
.nth(rowIdx)
@@ -8,7 +8,7 @@ export const getCell = async (loc: Page | Locator, rowIdx: number, colIdx: numbe
.nth(colIdx);
export const getCellHeight = async (loc: Page | Locator, rowIdx: number, colIdx: number) => {
const cell = await getCell(loc, rowIdx, colIdx);
const cell = getCell(loc, rowIdx, colIdx);
return (await cell.boundingBox())?.height ?? 0;
};
@@ -18,7 +18,7 @@ export const getColumnIdx = async (loc: Page | Locator, columnName: string) => {
let result = -1;
const colCount = await loc.getByRole('columnheader').count();
for (let colIdx = 0; colIdx < colCount; colIdx++) {
const cell = await getCell(loc, 0, colIdx);
const cell = getCell(loc, 0, colIdx);
if ((await cell.textContent()) === columnName) {
result = colIdx;
break;

View File

@@ -38,7 +38,7 @@ test.describe(
formatExpectError('Could not locate header elements in table panel')
).toContainText(['col1', 'col2']);
await expect(
panelEditPage.panel.locator.getByRole('gridcell'),
panelEditPage.panel.data,
formatExpectError('Could not locate headers in table panel')
).toContainText(['val1', 'val2', 'val3', 'val4']);
});
@@ -58,7 +58,7 @@ test.describe(
formatExpectError('Could not locate header elements in table panel')
).toContainText(['col1', 'col2']);
await expect(
panelEditPage.panel.locator.getByRole('gridcell'),
panelEditPage.panel.data,
formatExpectError('Could not locate data elements in table panel')
).toContainText(['val1', 'val2', 'val3', 'val4']);
});

View File

@@ -1,6 +1,6 @@
{
"name": "@test-plugins/extensions-test-app",
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"private": true,
"scripts": {
"build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production",

View File

@@ -1,6 +1,6 @@
{
"name": "@test-plugins/grafana-e2etest-datasource",
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"private": true,
"scripts": {
"build": "NODE_OPTIONS='--experimental-strip-types --no-warnings=ExperimentalWarning' webpack -c ./webpack.config.ts --env production",

View File

@@ -17,60 +17,24 @@ test.describe(
});
test.describe('App Page', () => {
test.describe('Sandbox disabled', () => {
test.beforeEach(async ({ page }) => {
await page.evaluate(() => {
localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0');
});
});
test('Loads the app page with the sandbox div wrapper', async ({ page }) => {
await page.goto(`/a/${APP_ID}`);
test('Loads the app page without the sandbox div wrapper', async ({ page }) => {
await page.goto(`/a/${APP_ID}`);
const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]');
await expect(sandboxDiv).toBeVisible();
const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]');
await expect(sandboxDiv).toBeHidden();
const appPage = page.getByTestId('sandbox-app-test-page-one');
await expect(appPage).toBeVisible();
});
test('Loads the app configuration without the sandbox div wrapper', async ({ page }) => {
await page.goto(`/plugins/${APP_ID}`);
const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]');
await expect(sandboxDiv).toBeHidden();
const configPage = page.getByTestId('sandbox-app-test-config-page');
await expect(configPage).toBeVisible();
});
const appPage = page.getByTestId('sandbox-app-test-page-one');
await expect(appPage).toBeVisible();
});
test.describe('Sandbox enabled', () => {
test.beforeEach(async ({ page }) => {
await page.evaluate(() => {
localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1');
});
});
test('Loads the app configuration with the sandbox div wrapper', async ({ page }) => {
await page.goto(`/plugins/${APP_ID}`);
test('Loads the app page with the sandbox div wrapper', async ({ page }) => {
await page.goto(`/a/${APP_ID}`);
const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]');
await expect(sandboxDiv).toBeVisible();
const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]');
await expect(sandboxDiv).toBeVisible();
const appPage = page.getByTestId('sandbox-app-test-page-one');
await expect(appPage).toBeVisible();
});
test('Loads the app configuration with the sandbox div wrapper', async ({ page }) => {
await page.goto(`/plugins/${APP_ID}`);
const sandboxDiv = page.locator('div[data-plugin-sandbox="sandbox-app-test"]');
await expect(sandboxDiv).toBeVisible();
const configPage = page.getByTestId('sandbox-app-test-config-page');
await expect(configPage).toBeVisible();
});
const configPage = page.getByTestId('sandbox-app-test-config-page');
await expect(configPage).toBeVisible();
});
});
}

View File

@@ -11,246 +11,127 @@ test.describe(
},
() => {
test.describe('Config Editor', () => {
test.describe('Sandbox disabled', () => {
test.beforeEach(async ({ page }) => {
await page.evaluate(() => {
localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0');
});
test('Should render a sandbox wrapper around the datasource config editor', async ({
page,
createDataSource,
}) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`);
test('Should not render a sandbox wrapper around the datasource config editor', async ({
page,
createDataSource,
}) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`);
const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`);
await expect(sandboxDiv).toBeHidden();
});
const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`);
await expect(sandboxDiv).toBeVisible();
});
test.describe('Sandbox enabled', () => {
test.beforeEach(async ({ page }) => {
await page.evaluate(() => {
localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1');
});
test('Should store values in jsonData and secureJsonData correctly', async ({ page, createDataSource }) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`);
test('Should render a sandbox wrapper around the datasource config editor', async ({
page,
createDataSource,
}) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`);
const valueToStore = 'test' + random(100);
const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`);
await expect(sandboxDiv).toBeVisible();
});
const queryInput = page.locator('[data-testid="sandbox-config-editor-query-input"]');
await expect(queryInput).not.toBeDisabled();
await queryInput.fill(valueToStore);
await expect(queryInput).toHaveValue(valueToStore);
test('Should store values in jsonData and secureJsonData correctly', async ({ page, createDataSource }) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`);
const saveButton = page.getByTestId('data-testid Data source settings page Save and Test button');
await saveButton.click();
const valueToStore = 'test' + random(100);
const alert = page.getByTestId('data-testid Data source settings page Alert');
await expect(alert).toBeVisible();
await expect(alert).toContainText('Sandbox Success');
const queryInput = page.locator('[data-testid="sandbox-config-editor-query-input"]');
await expect(queryInput).not.toBeDisabled();
await queryInput.fill(valueToStore);
await expect(queryInput).toHaveValue(valueToStore);
const saveButton = page.getByTestId('data-testid Data source settings page Save and Test button');
await saveButton.click();
const alert = page.getByTestId('data-testid Data source settings page Alert');
await expect(alert).toBeVisible();
await expect(alert).toContainText('Sandbox Success');
// validate the value was stored
await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`);
await expect(queryInput).not.toBeDisabled();
await expect(queryInput).toHaveValue(valueToStore);
});
// validate the value was stored
await page.goto(`/connections/datasources/edit/${DATASOURCE_CONNECTION_ID}`);
await expect(queryInput).not.toBeDisabled();
await expect(queryInput).toHaveValue(valueToStore);
});
});
test.describe('Explore Page', () => {
test.describe('Sandbox disabled', () => {
test.beforeEach(async ({ page }) => {
await page.evaluate(() => {
localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=0');
});
test('Should wrap the query editor in a sandbox wrapper', async ({
page,
createDataSource,
dashboardPage,
selectors,
}) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto('/explore');
test('Should not wrap the query editor in a sandbox wrapper', async ({
page,
createDataSource,
dashboardPage,
selectors,
}) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto('/explore');
const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container);
await expect(dataSourcePicker).toBeVisible();
await dataSourcePicker.click();
const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container);
await expect(dataSourcePicker).toBeVisible();
await dataSourcePicker.click();
const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`);
await expect(datasourceOption).toBeVisible();
await datasourceOption.scrollIntoViewIfNeeded();
await datasourceOption.click();
const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`);
await expect(datasourceOption).toBeVisible();
await datasourceOption.scrollIntoViewIfNeeded();
await datasourceOption.click();
// make sure the datasource was correctly selected and rendered
const breadcrumb = dashboardPage.getByGrafanaSelector(
selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME)
);
await expect(breadcrumb).toBeVisible();
// make sure the datasource was correctly selected and rendered
const breadcrumb = dashboardPage.getByGrafanaSelector(
selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME)
);
await expect(breadcrumb).toBeVisible();
const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`);
await expect(sandboxDiv).toBeHidden();
});
test('Should accept values when typed', async ({ page, createDataSource, dashboardPage, selectors }) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto('/explore');
const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container);
await expect(dataSourcePicker).toBeVisible();
await dataSourcePicker.click();
const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`);
await expect(datasourceOption).toBeVisible();
await datasourceOption.scrollIntoViewIfNeeded();
await datasourceOption.click();
// make sure the datasource was correctly selected and rendered
const breadcrumb = dashboardPage.getByGrafanaSelector(
selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME)
);
await expect(breadcrumb).toBeVisible();
const valueToType = 'test' + random(100);
const queryInput = page.locator('[data-testid="sandbox-query-editor-query-input"]');
await expect(queryInput).not.toBeDisabled();
await queryInput.fill(valueToType);
await expect(queryInput).toHaveValue(valueToType);
});
const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`);
await expect(sandboxDiv).toBeVisible();
});
test.describe('Sandbox enabled', () => {
test.beforeEach(async ({ page }) => {
await page.evaluate(() => {
localStorage.setItem('grafana.featureToggles', 'pluginsFrontendSandbox=1');
});
test('Should accept values when typed', async ({ page, createDataSource, dashboardPage, selectors }) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto('/explore');
test('Should wrap the query editor in a sandbox wrapper', async ({
page,
createDataSource,
dashboardPage,
selectors,
}) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto('/explore');
const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container);
await expect(dataSourcePicker).toBeVisible();
await dataSourcePicker.click();
const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container);
await expect(dataSourcePicker).toBeVisible();
await dataSourcePicker.click();
const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`);
await expect(datasourceOption).toBeVisible();
await datasourceOption.scrollIntoViewIfNeeded();
await datasourceOption.click();
const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`);
await expect(datasourceOption).toBeVisible();
await datasourceOption.scrollIntoViewIfNeeded();
await datasourceOption.click();
// make sure the datasource was correctly selected and rendered
const breadcrumb = dashboardPage.getByGrafanaSelector(
selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME)
);
await expect(breadcrumb).toBeVisible();
// make sure the datasource was correctly selected and rendered
const breadcrumb = dashboardPage.getByGrafanaSelector(
selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME)
);
await expect(breadcrumb).toBeVisible();
const valueToType = 'test' + random(100);
const sandboxDiv = page.locator(`div[data-plugin-sandbox="${DATASOURCE_ID}"]`);
await expect(sandboxDiv).toBeVisible();
});
const queryInput = page.locator('[data-testid="sandbox-query-editor-query-input"]');
await expect(queryInput).not.toBeDisabled();
await queryInput.fill(valueToType);
await expect(queryInput).toHaveValue(valueToType);
test('Should accept values when typed', async ({ page, createDataSource, dashboardPage, selectors }) => {
const TIMESTAMP = Date.now();
const DATASOURCE_TYPED_NAME = `SandboxDatasourceInstance-${TIMESTAMP}`;
// Add the datasource
const response = await createDataSource({
type: DATASOURCE_ID,
name: DATASOURCE_TYPED_NAME,
});
const DATASOURCE_CONNECTION_ID = response.uid;
await page.goto('/explore');
const dataSourcePicker = dashboardPage.getByGrafanaSelector(selectors.components.DataSourcePicker.container);
await expect(dataSourcePicker).toBeVisible();
await dataSourcePicker.click();
const datasourceOption = page.locator(`text=${DATASOURCE_TYPED_NAME}`);
await expect(datasourceOption).toBeVisible();
await datasourceOption.scrollIntoViewIfNeeded();
await datasourceOption.click();
// make sure the datasource was correctly selected and rendered
const breadcrumb = dashboardPage.getByGrafanaSelector(
selectors.components.Breadcrumbs.breadcrumb(DATASOURCE_TYPED_NAME)
);
await expect(breadcrumb).toBeVisible();
const valueToType = 'test' + random(100);
const queryInput = page.locator('[data-testid="sandbox-query-editor-query-input"]');
await expect(queryInput).not.toBeDisabled();
await queryInput.fill(valueToType);
await expect(queryInput).toHaveValue(valueToType);
// typing the query editor should reflect in the url
await expect(page).toHaveURL(new RegExp(valueToType));
});
// typing the query editor should reflect in the url
await expect(page).toHaveURL(new RegExp(valueToType));
});
});

View File

@@ -167,6 +167,16 @@
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90",
"3fa414edcef6ad90"
],
[
@@ -289,7 +299,17 @@
"0000000000000074",
"0000000000000075",
"0000000000000076",
"0000000000000077"
"0000000000000077",
"0000000000000078",
"0000000000000079",
"000000000000007a",
"000000000000007b",
"000000000000007c",
"000000000000007d",
"000000000000007e",
"000000000000007f",
"0000000000000080",
"0000000000000081"
],
[
"",
@@ -411,7 +431,17 @@
"0000000000000039",
"000000000000003a",
"000000000000003a",
"000000000000003b"
"000000000000003b",
"000000000000003b",
"0000000000000078",
"0000000000000078",
"0000000000000079",
"0000000000000079",
"000000000000007a",
"000000000000007a",
"000000000000007b",
"000000000000007b",
"000000000000007c"
],
[
"GET /api/health",
@@ -533,7 +563,17 @@
"GET /rollback/prepare",
"POST /commit/save",
"GET /branch/merge",
"POST /tag/create"
"POST /tag/create",
"GET /release/notes",
"POST /changelog/generate",
"GET /documentation/build",
"POST /test/run",
"GET /coverage/report",
"POST /artifact/publish",
"GET /registry/pull",
"POST /image/push",
"GET /manifest/inspect",
"POST /deployment/verify"
],
[
"api-gateway",
@@ -655,7 +695,17 @@
"rollback-mgr",
"git-api",
"merge-api",
"tag-api"
"tag-api",
"release-api",
"changelog-gen",
"doc-builder",
"test-runner",
"coverage-tool",
"artifact-mgr",
"registry-client",
"image-pusher",
"manifest-parser",
"deploy-verifier"
],
[
1579270400000, 1579270401000, 1579270402000, 1579270403000, 1579270404000, 1579270405000, 1579270406000,
@@ -675,7 +725,8 @@
1579270498000, 1579270499000, 1579270500000, 1579270501000, 1579270502000, 1579270503000, 1579270504000,
1579270505000, 1579270506000, 1579270507000, 1579270508000, 1579270509000, 1579270510000, 1579270511000,
1579270512000, 1579270513000, 1579270514000, 1579270515000, 1579270516000, 1579270517000, 1579270518000,
1579270519000
1579270519000, 1579270520000, 1579270521000, 1579270522000, 1579270523000, 1579270524000, 1579270525000,
1579270526000, 1579270527000, 1579270528000, 1579270529000
],
[
100000, 50000, 75000, 200000, 25000, 150000, 80000, 300000, 45000, 120000, 60000, 180000, 35000, 250000,
@@ -686,7 +737,8 @@
190000, 58000, 145000, 92000, 220000, 48000, 175000, 85000, 205000, 65000, 180000, 72000, 195000, 55000,
160000, 98000, 225000, 62000, 140000, 78000, 200000, 48000, 165000, 85000, 215000, 58000, 150000, 88000,
230000, 45000, 175000, 72000, 185000, 62000, 155000, 95000, 210000, 52000, 170000, 82000, 195000, 68000,
145000, 92000, 235000, 58000, 160000, 75000, 180000, 65000
145000, 92000, 235000, 58000, 160000, 75000, 180000, 65000, 125000, 78000, 190000, 52000, 165000, 88000,
215000, 62000, 155000, 95000
]
]
}

View File

@@ -33,15 +33,15 @@ describe('Trace view', () => {
e2e.components.TraceViewer.spanBar().should('be.visible');
e2e.components.TraceViewer.spanBar().its('length').should('be.equal', 100);
e2e.pages.Explore.General.scrollView().children().first().scrollTo('bottom');
// After scrolling we should see 50 spans
e2e.components.TraceViewer.spanBar()
.its('length')
.then((oldLength) => {
e2e.pages.Explore.General.scrollView().children().first().scrollTo('center');
// After scrolling we should load more spans
e2e.components.TraceViewer.spanBar().should(($span) => {
expect($span.length).to.be.gt(oldLength);
});
.should(($span) => {
expect($span).to.be.equal(50);
});
});
});

View File

@@ -2886,31 +2886,11 @@
"count": 1
}
},
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanBarRow.tsx": {
"react-prefer-function-component/react-prefer-function-component": {
"count": 1
}
},
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetail/SpanDetailLinkButtons.tsx": {
"react-hooks/rules-of-hooks": {
"count": 1
}
},
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanDetailRow.tsx": {
"react-prefer-function-component/react-prefer-function-component": {
"count": 1
}
},
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanLinks.tsx": {
"no-restricted-syntax": {
"count": 1
}
},
"public/app/features/explore/TraceView/components/TraceTimelineViewer/SpanTreeOffset.tsx": {
"react-prefer-function-component/react-prefer-function-component": {
"count": 1
}
},
"public/app/features/explore/TraceView/components/TraceTimelineViewer/TimelineHeaderRow/TimelineColumnResizer.tsx": {
"react-prefer-function-component/react-prefer-function-component": {
"count": 1
@@ -3313,11 +3293,6 @@
"count": 3
}
},
"public/app/features/provisioning/Config/ConfigFormGithubCollapse.tsx": {
"no-restricted-syntax": {
"count": 2
}
},
"public/app/features/provisioning/Shared/BranchValidationError.tsx": {
"react/no-unescaped-entities": {
"count": 26

32
go.mod
View File

@@ -32,7 +32,7 @@ require (
github.com/apache/arrow-go/v18 v18.4.1 // @grafana/plugins-platform-backend
github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad
github.com/aws/aws-sdk-go v1.55.7 // @grafana/aws-datasources
github.com/aws/aws-sdk-go-v2 v1.38.1 // @grafana/aws-datasources
github.com/aws/aws-sdk-go-v2 v1.39.1 // @grafana/aws-datasources
github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.45.3 // @grafana/aws-datasources
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 // @grafana/aws-datasources
github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 // @grafana/aws-datasources
@@ -63,7 +63,7 @@ require (
github.com/go-jose/go-jose/v4 v4.1.2 // @grafana/identity-access-team
github.com/go-kit/log v0.2.1 // @grafana/grafana-backend-group
github.com/go-ldap/ldap/v3 v3.4.4 // @grafana/identity-access-team
github.com/go-logfmt/logfmt v0.6.0 // @grafana/oss-big-tent
github.com/go-logfmt/logfmt v0.6.1 // @grafana/oss-big-tent
github.com/go-openapi/loads v0.23.1 // @grafana/alerting-backend
github.com/go-openapi/runtime v0.28.0 // @grafana/alerting-backend
github.com/go-openapi/strfmt v0.24.0 // @grafana/alerting-backend
@@ -98,7 +98,7 @@ require (
github.com/grafana/grafana-api-golang-client v0.27.0 // @grafana/alerting-backend
github.com/grafana/grafana-app-sdk v0.48.1 // @grafana/grafana-app-platform-squad
github.com/grafana/grafana-app-sdk/logging v0.48.1 // @grafana/grafana-app-platform-squad
github.com/grafana/grafana-aws-sdk v1.2.0 // @grafana/aws-datasources
github.com/grafana/grafana-aws-sdk v1.3.0 // @grafana/aws-datasources
github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 // @grafana/partner-datasources
github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 // @grafana/grafana-operator-experience-squad
github.com/grafana/grafana-google-sdk-go v0.4.2 // @grafana/partner-datasources
@@ -106,7 +106,7 @@ require (
github.com/grafana/grafana-plugin-sdk-go v0.281.0 // @grafana/plugins-platform-backend
github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 // @grafana/alerting-backend
github.com/grafana/loki/v3 v3.2.1 // @grafana/observability-logs
github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 // indirect; @grafana/grafana-git-ui-sync-team
github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b // indirect; @grafana/grafana-git-ui-sync-team
github.com/grafana/otel-profiling-go v0.5.1 // @grafana/grafana-backend-group
github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // @grafana/observability-traces-and-profiling
github.com/grafana/pyroscope/api v1.2.1-0.20250415190842-3ff7247547ae // @grafana/observability-traces-and-profiling
@@ -172,7 +172,7 @@ require (
github.com/stretchr/testify v1.11.1 // @grafana/grafana-backend-group
github.com/testcontainers/testcontainers-go v0.36.0 //@grafana/grafana-app-platform-squad
github.com/thomaspoignant/go-feature-flag v1.42.0 // @grafana/grafana-backend-group
github.com/tjhop/slog-gokit v0.1.3 // @grafana/grafana-app-platform-squad
github.com/tjhop/slog-gokit v0.1.5 // @grafana/grafana-app-platform-squad
github.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90 // @grafana/grafana-backend-group
github.com/urfave/cli v1.22.17 // indirect; @grafana/grafana-backend-group
github.com/urfave/cli/v2 v2.27.7 // @grafana/grafana-backend-group
@@ -237,6 +237,7 @@ require (
github.com/grafana/grafana/apps/alerting/alertenrichment v0.0.0 // @grafana/alerting-backend
github.com/grafana/grafana/apps/alerting/notifications v0.0.0 // @grafana/alerting-backend
github.com/grafana/grafana/apps/alerting/rules v0.0.0 // @grafana/alerting-backend
github.com/grafana/grafana/apps/annotation v0.0.0 // @grafana/grafana-backend-services-squad
github.com/grafana/grafana/apps/correlations v0.0.0 // @grafana/datapro
github.com/grafana/grafana/apps/dashboard v0.0.0 // @grafana/grafana-app-platform-squad @grafana/dashboards-squad
github.com/grafana/grafana/apps/example v0.0.0-20251027162426-edef69fdc82b // @grafana/grafana-app-platform-squad
@@ -268,6 +269,7 @@ replace (
github.com/grafana/grafana/apps/alerting/alertenrichment => ./apps/alerting/alertenrichment
github.com/grafana/grafana/apps/alerting/notifications => ./apps/alerting/notifications
github.com/grafana/grafana/apps/alerting/rules => ./apps/alerting/rules
github.com/grafana/grafana/apps/annotation => ./apps/annotation
github.com/grafana/grafana/apps/correlations => ./apps/correlations
github.com/grafana/grafana/apps/dashboard => ./apps/dashboard
github.com/grafana/grafana/apps/folder => ./apps/folder
@@ -332,23 +334,23 @@ require (
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
github.com/at-wat/mqtt-go v0.19.4 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect
github.com/aws/aws-sdk-go-v2/config v1.31.2 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.6 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 // indirect
github.com/aws/aws-sdk-go-v2/config v1.31.10 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.18.14 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8 // indirect
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect
github.com/aws/aws-sdk-go-v2/service/kms v1.41.2 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.29.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 // indirect
github.com/axiomhq/hyperloglog v0.0.0-20240507144631-af9851f82b27 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/barkimedes/go-deepcopy v0.0.0-20220514131651-17c30cfc62df // indirect

60
go.sum
View File

@@ -846,22 +846,22 @@ github.com/aws/aws-sdk-go v1.38.35/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8=
github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE=
github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY=
github.com/aws/aws-sdk-go-v2/config v1.31.2 h1:NOaSZpVGEH2Np/c1toSeW0jooNl+9ALmsUTZ8YvkJR0=
github.com/aws/aws-sdk-go-v2/config v1.31.2/go.mod h1:17ft42Yb2lF6OigqSYiDAiUcX4RIkEMY6XxEMJsrAes=
github.com/aws/aws-sdk-go-v2/credentials v1.18.6 h1:AmmvNEYrru7sYNJnp3pf57lGbiarX4T9qU/6AZ9SucU=
github.com/aws/aws-sdk-go-v2/credentials v1.18.6/go.mod h1:/jdQkh1iVPa01xndfECInp1v1Wnp70v3K4MvtlLGVEc=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 h1:lpdMwTzmuDLkgW7086jE94HweHCqG+uOJwHf3LZs7T0=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY=
github.com/aws/aws-sdk-go-v2/config v1.31.10 h1:7LllDZAegXU3yk41mwM6KcPu0wmjKGQB1bg99bNdQm4=
github.com/aws/aws-sdk-go-v2/config v1.31.10/go.mod h1:Ge6gzXPjqu4v0oHvgAwvGzYcK921GU0hQM25WF/Kl+8=
github.com/aws/aws-sdk-go-v2/credentials v1.18.14 h1:TxkI7QI+sFkTItN/6cJuMZEIVMFXeu2dI1ZffkXngKI=
github.com/aws/aws-sdk-go-v2/credentials v1.18.14/go.mod h1:12x4Uw/vijC11XkctTjy92TNCQ+UnNJkT7fzX0Yd93E=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8 h1:gLD09eaJUdiszm7vd1btiQUYE0Hj+0I2b8AS+75z9AY=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.8/go.mod h1:4RW3oMPt1POR74qVOC4SbubxAwdP4pCT0nSw3jycOU4=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84 h1:cTXRdLkpBanlDwISl+5chq5ui1d1YWg4PWMR9c3kXyw=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.84/go.mod h1:kwSy5X7tfIHN39uucmjQVs2LvDdXEjQucgQQEqCggEo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 h1:IdCLsiiIj5YJ3AFevsewURCPV+YWUlOW8JiPhoAy8vg=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 h1:j7vjtr1YIssWQOMeOWRbh3z8g2oY/xPjnZH2gLY4sGw=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8/go.mod h1:KcGkXFVU8U28qS4KvLEcPxytPZPBcRawaH2Pf/0jptE=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8/go.mod h1:JnA+hPWeYAVbDssp83tv+ysAG8lTfLVXvSsyKg/7xNA=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs=
@@ -872,12 +872,12 @@ github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0 h1:e5cbPZYTIY2nUEFie
github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.51.0/go.mod h1:UseIHRfrm7PqeZo6fcTb6FUCXzCnh1KJbQbmOfxArGM=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2 h1:IfMb3Ar8xEaWjgH/zeVHYD8izwJdQgRP5mKCTDt4GNk=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.225.2/go.mod h1:35jGWx7ECvCwTsApqicFYzZ7JFEnBc6oHUuOQ3xIS54=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0NacDMnaC+68za9jLwkjzxGWm0KB5HTUHjLTI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8/go.mod h1:Fw+MyTwlwjFsSTE31mH211Np+CUslml8mzc0AFEG09s=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U=
github.com/aws/aws-sdk-go-v2/service/kms v1.41.2 h1:zJeUxFP7+XP52u23vrp4zMcVhShTWbNO8dHV6xCSvFo=
@@ -888,12 +888,12 @@ github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6 h1:Pwbxovp
github.com/aws/aws-sdk-go-v2/service/resourcegroupstaggingapi v1.26.6/go.mod h1:Z4xLt5mXspLKjBV92i165wAJ/3T6TIv4n7RtIS8pWV0=
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0 h1:0reDqfEN+tB+sozj2r92Bep8MEwBZgtAXTND1Kk9OXg=
github.com/aws/aws-sdk-go-v2/service/s3 v1.84.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU=
github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 h1:ve9dYBB8CfJGTFqcQ3ZLAAb/KXWgYlgu/2R2TZL2Ko0=
github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 h1:pd9G9HQaM6UZAZh19pYOkpKSQkyQQ9ftnl/LttQOcGI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.4 h1:FTdEN9dtWPB0EOURNtDPmwGp6GGvMqRJCAihkSl/1No=
github.com/aws/aws-sdk-go-v2/service/sso v1.29.4/go.mod h1:mYubxV9Ff42fZH4kexj43gFPhgc/LyC7KqvUKt1watc=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0 h1:I7ghctfGXrscr7r1Ga/mDqSJKm7Fkpl5Mwq79Z+rZqU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.0/go.mod h1:Zo9id81XP6jbayIFWNuDpA6lMBWhsVy+3ou2jLa4JnA=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 h1:+LVB0xBqEgjQoqr9bGZbRzvg212B0f17JdflleJRNR4=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5/go.mod h1:xoaxeqnnUaZjPjaICgIy5B+MHCSb/ZSOn4MvkFNOUA0=
github.com/aws/smithy-go v1.23.1 h1:sLvcH6dfAFwGkHLZ7dGiYF7aK6mg4CgKA/iDKjLDt9M=
github.com/aws/smithy-go v1.23.1/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0=
github.com/axiomhq/hyperloglog v0.0.0-20191112132149-a4c4c47bc57f/go.mod h1:2stgcRjl6QmW+gU2h5E7BQXg4HU0gzxKWDuT5HviN9s=
@@ -1248,8 +1248,8 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
@@ -1637,8 +1637,8 @@ github.com/grafana/grafana-app-sdk v0.48.1 h1:bKJadWH18WCpJ+Zk8AezRFXCcZgGredRv+
github.com/grafana/grafana-app-sdk v0.48.1/go.mod h1:5LljCz+wvmGfkQ8ZKTOfserhtXNEF0cSFthoWShvN6c=
github.com/grafana/grafana-app-sdk/logging v0.48.1 h1:veM0X5LAPyN3KsDLglWjIofndbGuf7MqnrDuDN+F/Ng=
github.com/grafana/grafana-app-sdk/logging v0.48.1/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
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-aws-sdk v1.3.0 h1:/bfJzP93rCel1GbWoRSq0oUo424MZXt8jAp2BK9w8tM=
github.com/grafana/grafana-aws-sdk v1.3.0/go.mod h1:VGycF0JkCGKND2O5je1ucOqPJ0ZNhZYzV3c2bNBAaGk=
github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1 h1:FFcEA01tW+SmuJIuDbHOdgUBL+d7DPrZ2N4zwzPhfGk=
github.com/grafana/grafana-azure-sdk-go/v2 v2.3.1/go.mod h1:Oi4anANlCuTCc66jCyqIzfVbgLXFll8Wja+Y4vfANlc=
github.com/grafana/grafana-cloud-migration-snapshot v1.9.0 h1:JOzchPgptwJdruYoed7x28lFDwhzs7kssResYsnC0iI=
@@ -1661,8 +1661,8 @@ github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000 h1:/5LKSYgLm
github.com/grafana/loki/pkg/push v0.0.0-20250823105456-332df2b20000/go.mod h1:/ZklAgE1i4f3Z8uriXwESmCr1VLF8lBGaJspuaGuf78=
github.com/grafana/loki/v3 v3.2.1 h1:VB7u+KHfvL5aHAxgoVBvz5wVhsdGuqKC7uuOFOOe7jw=
github.com/grafana/loki/v3 v3.2.1/go.mod h1:WvdLl6wOS+yahaeQY+xhD2m2XzkHDfKr5FZaX7D/X2Y=
github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0 h1:cS0SlJGIlZbmDLctNj5vIYGemrJDLy25wwoiIyZWVN8=
github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA=
github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b h1:rFjoqJFb2KxJ29K9ltuWRSsdA46SbN0GCxoQc36h5kg=
github.com/grafana/nanogit v0.0.0-20251106115617-c622d3e0fc4b/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA=
github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8=
github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250911094103-5456b6e45604 h1:aXfUhVN/Ewfpbko2CCtL65cIiGgwStOo4lWH2b6gw2U=
@@ -2499,8 +2499,8 @@ github.com/thomaspoignant/go-feature-flag v1.42.0 h1:C7embmOTzaLyRki+OoU2RvtVjJE
github.com/thomaspoignant/go-feature-flag v1.42.0/go.mod h1:y0QiWH7chHWhGATb/+XqwAwErORmPSH2MUsQlCmmWlM=
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tjhop/slog-gokit v0.1.3 h1:6SdexP3UIeg93KLFeiM1Wp1caRwdTLgsD/THxBUy1+o=
github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs=
github.com/tjhop/slog-gokit v0.1.5 h1:ayloIUi5EK2QYB8eY4DOPO95/mRtMW42lUkp3quJohc=
github.com/tjhop/slog-gokit v0.1.5/go.mod h1:yA48zAHvV+Sg4z4VRyeFyFUNNXd3JY5Zg84u3USICq0=
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=

View File

@@ -9,6 +9,7 @@ use (
./apps/alerting/alertenrichment
./apps/alerting/notifications
./apps/alerting/rules
./apps/annotation
./apps/correlations
./apps/dashboard
./apps/example

View File

@@ -275,6 +275,7 @@ github.com/Azure/azure-sdk-for-go/sdk/messaging/azservicebus v1.9.1/go.mod h1:Ny
github.com/Azure/go-amqp v0.17.0/go.mod h1:9YJ3RhxRT1gquYnzpZO1vcYMMpAdJT+QEg6fwmw9Zlg=
github.com/Azure/go-amqp v1.4.0 h1:Xj3caqi4comOF/L1Uc5iuBxR/pB6KumejC01YQOqOR4=
github.com/Azure/go-amqp v1.4.0/go.mod h1:vZAogwdrkbyK3Mla8m/CxSc/aKdnTZ4IbPxl51Y5WZE=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13 h1:Ov8avRZi2vmrE2JcXw+tu5K/yB41r7xK9GZDiBF7NdM=
github.com/Azure/go-autorest/autorest/azure/auth v0.5.13/go.mod h1:5BAVfWLWXihP47vYrPuBKKf4cS0bXI+KM9Qx6ETDJYo=
github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc=
@@ -335,6 +336,8 @@ github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4Oe
github.com/Microsoft/go-winio v0.4.21/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Microsoft/hcsshim v0.11.5/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
github.com/MissingRoberto/slog-gokit v0.0.0-20251105092822-783f72952ce4 h1:gTtFbl79tuZSeJuSO7kXSbmXSvKSa/PoUXda1tuz0O8=
github.com/MissingRoberto/slog-gokit v0.0.0-20251105092822-783f72952ce4/go.mod h1:yA48zAHvV+Sg4z4VRyeFyFUNNXd3JY5Zg84u3USICq0=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
@@ -407,44 +410,75 @@ github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7Rfg
github.com/aws/aws-msk-iam-sasl-signer-go v1.0.1 h1:nMp7diZObd4XEVUR0pEvn7/E13JIgManMX79Q6quV6E=
github.com/aws/aws-msk-iam-sasl-signer-go v1.0.1/go.mod h1:MVYeeOhILFFemC/XlYTClvBjYZrg/EPd3ts885KrNTI=
github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0=
github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8=
github.com/aws/aws-sdk-go-v2/config v1.31.2/go.mod h1:17ft42Yb2lF6OigqSYiDAiUcX4RIkEMY6XxEMJsrAes=
github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc=
github.com/aws/aws-sdk-go-v2/credentials v1.18.6/go.mod h1:/jdQkh1iVPa01xndfECInp1v1Wnp70v3K4MvtlLGVEc=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.5 h1:oUEqVqonG3xuarrsze1KVJ30KagNYDemikTbdu8KlN8=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/attributevalue v1.19.5/go.mod h1:VNM08cHlOsIbSHRqb6D/M2L4kKXfJv3A2/f0GNbOQSc=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.7.87 h1:oDPArGgCrG/4aTi86ij3S2PB59XXkTSKYVNQlmqRHXQ=
github.com/aws/aws-sdk-go-v2/feature/dynamodb/expression v1.7.87/go.mod h1:ZeQC4gVarhdcWeM1c90DyBLaBCNhEeAbKUXwVI/byvw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY=
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.69/go.mod h1:GJj8mmO6YT6EqgduWocwhMoxTLFitkhIrK+owzrYL2I=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34/go.mod h1:zf7Vcd1ViW7cPqYWEHLHJkS50X0JS2IKz9Cgaj6ugrs=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.0 h1:A99gjqZDbdhjtjJVZrmVzVKO2+p3MSg35bDWtbMQVxw=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.44.0/go.mod h1:mWB0GE1bqcVSvpW7OtFA0sKuHk52+IqtnsYU2jUfYAs=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.26.0 h1:0wOCTKrmwkyC8Bk76hYH/B4IJn5MGt6gMkSXc0A2uyc=
github.com/aws/aws-sdk-go-v2/service/dynamodbstreams v1.26.0/go.mod h1:He/RikglWUczbkV+fkdpcV/3GdL/rTRNVy7VaUiezMo=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0/go.mod h1:iu6FSzgt+M2/x3Dk8zhycdIcHjEFb36IS8HVUVFoMg0=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.17 h1:x187MqiHwBGjMGAed8Y8K1VGuCtFvQvXb24r+bwmSdo=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.10.17/go.mod h1:mC9qMbA6e1pwEq6X3zDGtZRXMG2YaElJkbJlMVHLs5I=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15/go.mod h1:ZH34PJUc8ApjBIfgQCFvkWcUDBtl/WTD+uiYHjd8igA=
github.com/aws/aws-sdk-go-v2/service/kinesis v1.33.0 h1:JPXkrQk5OS/+Q81fKH97Ll/Vmmy0p9vwHhxw+V+tVjg=
github.com/aws/aws-sdk-go-v2/service/kinesis v1.33.0/go.mod h1:dJngkoVMrq0K7QvRkdRZYM4NUp6cdWa2GBdpm8zoY8U=
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3 h1:UPTdlTOwWUX49fVi7cymEN6hDqCwe3LNv1vi7TXUutk=
github.com/aws/aws-sdk-go-v2/service/kms v1.35.3/go.mod h1:gjDP16zn+WWalyaUqwCCioQ8gU8lzttCCc9jYsiQI/8=
github.com/aws/aws-sdk-go-v2/service/kms v1.38.1/go.mod h1:cQn6tAF77Di6m4huxovNM7NVAozWTZLsDRp9t8Z/WYk=
github.com/aws/aws-sdk-go-v2/service/s3 v1.78.2/go.mod h1:U5SNqwhXB3Xe6F47kXvWihPl/ilGaEDe8HD/50Z9wxc=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4 h1:NgRFYyFpiMD62y4VPXh4DosPFbZd4vdMVBWKk0VmWXc=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.32.4/go.mod h1:TKKN7IQoM7uTnyuFm9bm9cw5P//ZYTl4m3htBWQ1G/c=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.2 h1:vlYXbindmagyVA3RS2SPd47eKZ00GZZQcr+etTviHtc=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.2/go.mod h1:yGhDiLKguA3iFJYxbrQkQiNzuy+ddxesSZYWVeeEH5Q=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.7 h1:d+mnMa4JbJlooSbYQfrJpit/YINaB30JEVgrhtjZneA=
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.35.7/go.mod h1:1X1NotbcGHH7PCQJ98PsExSxsJj/VWzz8MfFz43+02M=
github.com/aws/aws-sdk-go-v2/service/sns v1.31.3 h1:eSTEdxkfle2G98FE+Xl3db/XAXXVTJPNQo9K/Ar8oAI=
github.com/aws/aws-sdk-go-v2/service/sns v1.31.3/go.mod h1:1dn0delSO3J69THuty5iwP0US2Glt0mx2qBBlI13pvw=
github.com/aws/aws-sdk-go-v2/service/sns v1.34.2 h1:PajtbJ/5bEo6iUAIGMYnK8ljqg2F1h4mMCGh1acjN30=
github.com/aws/aws-sdk-go-v2/service/sns v1.34.2/go.mod h1:PJtxxMdj747j8DeZENRTTYAz/lx/pADn/U0k7YNNiUY=
github.com/aws/aws-sdk-go-v2/service/sns v1.34.7 h1:OBuZE9Wt8h2imuRktu+WfjiTGrnYdCIJg8IX92aalHE=
github.com/aws/aws-sdk-go-v2/service/sns v1.34.7/go.mod h1:4WYoZAhHt+dWYpoOQUgkUKfuQbE6Gg/hW4oXE0pKS9U=
github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3 h1:Vjqy5BZCOIsn4Pj8xzyqgGmsSqzz7y/WXbN3RgOoVrc=
github.com/aws/aws-sdk-go-v2/service/sqs v1.34.3/go.mod h1:L0enV3GCRd5iG9B64W35C4/hwsCB00Ib+DKVGTadKHI=
github.com/aws/aws-sdk-go-v2/service/sqs v1.38.3 h1:j5BchjfDoS7K26vPdyJlyxBIIBGDflq3qjjJKBDlbcI=
github.com/aws/aws-sdk-go-v2/service/sqs v1.38.3/go.mod h1:Bar4MrRxeqdn6XIh8JGfiXuFRmyrrsZNTJotxEJmWW0=
github.com/aws/aws-sdk-go-v2/service/sqs v1.38.8 h1:80dpSqWMwx2dAm30Ib7J6ucz1ZHfiv5OCRwN/EnCOXQ=
github.com/aws/aws-sdk-go-v2/service/sqs v1.38.8/go.mod h1:IzNt/udsXlETCdvBOL0nmyMe2t9cGmXmZgsdoZGYYhI=
github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4 h1:hgSBvRT7JEWx2+vEGI9/Ld5rZtl7M5lu8PqdvOmbRHw=
github.com/aws/aws-sdk-go-v2/service/ssm v1.52.4/go.mod h1:v7NIzEFIHBiicOMaMTuEmbnzGnqW0d+6ulNALul6fYE=
github.com/aws/aws-sdk-go-v2/service/ssm v1.58.0 h1:zQz6Q5uaC8s9734DV9UDAm2q1TEEfOvEejDBSulOapI=
github.com/aws/aws-sdk-go-v2/service/ssm v1.58.0/go.mod h1:PUWUl5MDiYNQkUHN9Pyd9kgtA/YhbxnSnHP+yQqzrM8=
github.com/aws/aws-sdk-go-v2/service/ssm v1.60.1 h1:OwMzNDe5VVTXD4kGmeK/FtqAITiV8Mw4TCa8IyNO0as=
github.com/aws/aws-sdk-go-v2/service/ssm v1.60.1/go.mod h1:IyVabkWrs8SNdOEZLyFFcW9bUltV4G6OQS0s6H20PHg=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc=
github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM=
github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w=
github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo=
github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/awslabs/aws-lambda-go-api-proxy v0.16.2 h1:CJyGEyO1CIwOnXTU40urf0mchf6t3voxpvUDikOU9LY=
github.com/awslabs/aws-lambda-go-api-proxy v0.16.2/go.mod h1:vxxjwBHe/KbgFeNlAP/Tvp4SsVRL3WQamcWRxqVh0z0=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
@@ -549,7 +583,6 @@ github.com/couchbase/ghistogram v0.1.0 h1:b95QcQTCzjTUocDXp/uMgSNQi8oj1tGwnJ4bOD
github.com/couchbase/ghistogram v0.1.0/go.mod h1:s1Jhy76zqfEecpNWJfWUiKZookAFaiGOEoyzgHt9i7k=
github.com/couchbase/moss v0.2.0 h1:VCYrMzFwEryyhRSeI+/b3tRBSeTpi/8gn5Kf6dxqn+o=
github.com/couchbase/moss v0.2.0/go.mod h1:9MaHIaRuy9pvLPUJxB8sh8OrLfyDczECVL37grCIubs=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -705,6 +738,7 @@ github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1:
github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4=
github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs=
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81 h1:6zl3BbBhdnMkpSj2YY30qV3gDcVBGtFgVsV3+/i+mKQ=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
@@ -804,13 +838,76 @@ github.com/grafana/dskit v0.0.0-20250818234656-8ff9c6532e85/go.mod h1:kImsvJ1xnm
github.com/grafana/go-gelf/v2 v2.0.1 h1:BOChP0h/jLeD+7F9mL7tq10xVkDG15he3T1zHuQaWak=
github.com/grafana/go-gelf/v2 v2.0.1/go.mod h1:lexHie0xzYGwCgiRGcvZ723bSNyNI8ZRD4s0CLobh90=
github.com/grafana/go-mysql-server v0.20.1-0.20251027172658-317a8d46ffa4/go.mod h1:EeYR0apo+8j2Dyxmn2ghkPlirO2S5mT1xHBrA+Efys8=
github.com/grafana/gomemcache v0.0.0-20250228145437-da7b95fd2ac1/go.mod h1:j/s0jkda4UXTemDs7Pgw/vMT06alWc42CHisvYac0qw=
github.com/grafana/grafana-app-sdk v0.40.1/go.mod h1:4P8h7VB6KcDjX9bAoBQc6IP8iNylxe6bSXLR9gA39gM=
github.com/grafana/grafana-app-sdk v0.40.2/go.mod h1:BbNXPNki3mtbkWxYqJsyA1Cj9AShSyaY33z8WkyfVv0=
github.com/grafana/grafana-app-sdk v0.41.0 h1:SYHN3U7B1myRKY3UZZDkFsue9TDmAOap0UrQVTqtYBU=
github.com/grafana/grafana-app-sdk v0.41.0/go.mod h1:Wg/3vEZfok1hhIWiHaaJm+FwkosfO98o8KbeLFEnZpY=
github.com/grafana/grafana-app-sdk v0.46.0/go.mod h1:LCTrqR1SwBS13XGVYveBmM7giJDDjzuXK+M9VzPuPWc=
github.com/grafana/grafana-app-sdk v0.47.0/go.mod h1:kywXmkppq0oReUMzkjTW8Fq2EBzyN7v914jttTWnWxA=
github.com/grafana/grafana-app-sdk/logging v0.38.0/go.mod h1:Y/bvbDhBiV/tkIle9RW49pgfSPIPSON8Q4qjx3pyqDk=
github.com/grafana/grafana-app-sdk/logging v0.39.0 h1:3GgN5+dUZYqq74Q+GT9/ET+yo+V54zWQk/Q2/JsJQB4=
github.com/grafana/grafana-app-sdk/logging v0.39.0/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk=
github.com/grafana/grafana-app-sdk/logging v0.39.1/go.mod h1:WhDENSnaGHtyVVwZGVnAR7YLvh2xlLDYR3D7E6h7XVk=
github.com/grafana/grafana-app-sdk/logging v0.40.0/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU=
github.com/grafana/grafana-app-sdk/logging v0.40.2/go.mod h1:otUD9XpJD7A5sCLb8mcs9hIXGdeV6lnhzVwe747g4RU=
github.com/grafana/grafana-app-sdk/logging v0.43.0/go.mod h1:0xrjKSGY5z+NLGuGsXQpxiCHR4Smu79i/CbAfdkaB1M=
github.com/grafana/grafana-app-sdk/logging v0.43.1/go.mod h1:0xrjKSGY5z+NLGuGsXQpxiCHR4Smu79i/CbAfdkaB1M=
github.com/grafana/grafana-app-sdk/logging v0.43.2/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grafana/grafana-app-sdk/logging v0.45.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grafana/grafana-app-sdk/logging v0.46.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grafana/grafana-app-sdk/logging v0.48.0 h1:xolkQxBlA2LQF4hprKIAeu+zUem1DigYZ6XC1TOhFJE=
github.com/grafana/grafana-app-sdk/logging v0.48.0/go.mod h1:Gh/nBWnspK3oDNWtiM5qUF/fardHzOIEez+SPI3JeHA=
github.com/grafana/grafana-app-sdk/plugin v0.41.0 h1:ShUvGpAVzM3UxcsfwS6l/lwW4ytDeTbCQXf8w2P8Yp8=
github.com/grafana/grafana-app-sdk/plugin v0.41.0/go.mod h1:YIhimVfAqtOp3kdhxOanaSZjypVKh/bYxf9wfFfhDm0=
github.com/grafana/grafana-aws-sdk v0.38.2 h1:TzQD0OpWsNjtldi5G5TLDlBRk8OyDf+B5ujcoAu4Dp0=
github.com/grafana/grafana-aws-sdk v0.38.2/go.mod h1:j3vi+cXYHEFqjhBGrI6/lw1TNM+dl0Y3f0cSnDOPy+s=
github.com/grafana/grafana-aws-sdk v1.0.2 h1:98eBuHYFmgvH0xO9kKf4RBsEsgQRp8EOA/9yhDIpkss=
github.com/grafana/grafana-aws-sdk v1.0.2/go.mod h1:hO7q7yWV+t6dmiyJjMa3IbuYnYkBua+G/IAlOPVIYKE=
github.com/grafana/grafana-aws-sdk v1.1.0/go.mod h1:7e+47EdHynteYWGoT5Ere9KeOXQObsk8F0vkOLQ1tz8=
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.1.6/go.mod h1:V7y2BmsWxS3A9Ohebwn4OiSfJJqi//4JQydQ8fHTduo=
github.com/grafana/grafana-azure-sdk-go/v2 v2.2.0/go.mod h1:H9sVh9A4yg5egMGZeh0mifxT1Q/uqwKe1LBjBJU6pN8=
github.com/grafana/grafana-plugin-sdk-go v0.263.0/go.mod h1:U43Cnrj/9DNYyvFcNdeUWNjMXTKNB0jcTcQGpWKd2gw=
github.com/grafana/grafana-plugin-sdk-go v0.267.0/go.mod h1:OuwS4c/JYgn0rr/w5zhJBpLo4gKm/vw15RsfpYAvK9Q=
github.com/grafana/grafana-plugin-sdk-go v0.269.1/go.mod h1:yv2KbO4mlr9WuDK2f+2gHAMTwwLmLuqaEnrPXTRU+OI=
github.com/grafana/grafana-plugin-sdk-go v0.275.0/go.mod h1:mO9LJqdXDh5JpO/xIdPAeg5LdThgQ06Y/SLpXDWKw2c=
github.com/grafana/grafana-plugin-sdk-go v0.277.0/go.mod h1:mAUWg68w5+1f5TLDqagIr8sWr1RT9h7ufJl5NMcWJAU=
github.com/grafana/grafana-plugin-sdk-go v0.278.0/go.mod h1:+8NXT/XUJ/89GV6FxGQ366NZ3nU+cAXDMd0OUESF9H4=
github.com/grafana/grafana-plugin-sdk-go v0.279.0/go.mod h1:/7oGN6Z7DGTGaLHhgIYrRr6Wvmdsb3BLw5hL4Kbjy88=
github.com/grafana/grafana-plugin-sdk-go v0.280.0/go.mod h1:Z15Wiq3c4I0tzHYrLYpOqrO8u3+2RJ+HN2Q9uiZTILA=
github.com/grafana/grafana/apps/advisor v0.0.0-20250123151950-b066a6313173/go.mod h1:goSDiy3jtC2cp8wjpPZdUHRENcoSUHae1/Px/MDfddA=
github.com/grafana/grafana/apps/advisor v0.0.0-20250220154326-6e5de80ef295/go.mod h1:9I1dKV3Dqr0NPR9Af0WJGxOytp5/6W3JLiNChOz8r+c=
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250121113133-e747350fee2d/go.mod h1:AvleS6icyPmcBjihtx5jYEvdzLmHGBp66NuE0AMR57A=
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250416173722-ec17e0e4ce03/go.mod h1:oemrhKvFxxc5m32xKHPxInEHAObH0/hPPyHUiBUZ1Cc=
github.com/grafana/grafana/apps/alerting/notifications v0.0.0-20250506052906-7a2fc797fb4a/go.mod h1:VkX53kBiqIMHBoGgeEDJnzm5Nwcmv/726tuZuT5SvJY=
github.com/grafana/grafana/apps/alerting/rules v0.0.0-20250731223157-26b18dda3364/go.mod h1:wi4njPm5mJ8IpK13h57be8sWoxOhqr1UQOwmXhRM9Gk=
github.com/grafana/grafana/apps/dashboard v0.0.0-20250616135341-59c2f154336b/go.mod h1:OIlvNnUufYDhBXa4xK4CyzPI2C69ZJkHy5+aFDyPtXw=
github.com/grafana/grafana/apps/dashboard v0.0.0-20250616145019-8d27f12428cb/go.mod h1:OIlvNnUufYDhBXa4xK4CyzPI2C69ZJkHy5+aFDyPtXw=
github.com/grafana/grafana/apps/dashboard v0.0.0-20250627191313-2f1a6ae1712b/go.mod h1:eR8wca74ADgxBrvX0uNpdB1qnPaGx/KhCm4Xj8oqHfQ=
github.com/grafana/grafana/apps/investigation v0.0.0-20250121113133-e747350fee2d/go.mod h1:HQprw3MmiYj5OUV9CZnkwA1FKDZBmYACuAB3oDvUOmI=
github.com/grafana/grafana/apps/playlist v0.0.0-20250121113133-e747350fee2d/go.mod h1:DjJe5osrW/BKrzN9hAAOSElNWutj1bcriExa7iDP7kA=
github.com/grafana/grafana/apps/preferences v0.0.0-20250805113453-4b17c24d67ff h1:JDT0Mcfpi3c525xzeli+v5dR9pf5HhdFjr8djRdhs10=
github.com/grafana/grafana/apps/preferences v0.0.0-20250805113453-4b17c24d67ff/go.mod h1:NQlHMO5fHhjexw71wVjv522532NRvFg5F4tcjUEktjs=
github.com/grafana/grafana/apps/preferences v0.0.0-20250805120145-0c5a00302924 h1:uGXX6gCF1q2ytIL0w1X3UAKgF/UZ7eDDAgOaSqLOeW8=
github.com/grafana/grafana/apps/preferences v0.0.0-20250805120145-0c5a00302924/go.mod h1:NQlHMO5fHhjexw71wVjv522532NRvFg5F4tcjUEktjs=
github.com/grafana/grafana/apps/preferences v0.0.0-20250805123034-066163d71001 h1:y2AHkdji2I+zXv8rsSC8OjWEzJJjqW5OlmCsZR5+RuU=
github.com/grafana/grafana/apps/preferences v0.0.0-20250805123034-066163d71001/go.mod h1:NQlHMO5fHhjexw71wVjv522532NRvFg5F4tcjUEktjs=
github.com/grafana/grafana/pkg/aggregator v0.0.0-20250121113133-e747350fee2d/go.mod h1:1sq0guad+G4SUTlBgx7SXfhnzy7D86K/LcVOtiQCiMA=
github.com/grafana/grafana/pkg/semconv v0.0.0-20250121113133-e747350fee2d/go.mod h1:tfLnBpPYgwrBMRz4EXqPCZJyCjEG4Ev37FSlXnocJ2c=
github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250121113133-e747350fee2d/go.mod h1:CXpwZ3Mkw6xVlGKc0SqUxqXCP3Uv182q6qAQnLaLxRg=
github.com/grafana/grafana/pkg/storage/unified/apistore v0.0.0-20250514132646-acbc7b54ed9e/go.mod h1:xrKQcxQxz+IUF90ybtfENFeEXtlj9nAsX/3Fw0KEIeQ=
github.com/grafana/nanogit v0.0.0-20250616082354-5e94194d02ed h1:59JF1WhHLT+lNX89Tm1OzOEySMVMASAhaPbsRjtp8Kc=
github.com/grafana/nanogit v0.0.0-20250616082354-5e94194d02ed/go.mod h1:OIAAKNgG5fpuJQRNO1lUSj9nc18Xl3O7M8fjIlBO1cI=
github.com/grafana/nanogit v0.0.0-20250619160700-ebf70d342aa5 h1:MAQ2B0cu0V1S91ZjVa7NomNZFjaR2SmdtvdwhqBtyhU=
github.com/grafana/nanogit v0.0.0-20250619160700-ebf70d342aa5/go.mod h1:tN93IZUaAmnSWgL0IgnKdLv6DNeIhTJGvl1wvQMrWco=
github.com/grafana/nanogit v0.0.0-20250723104447-68f58f5ecec0/go.mod h1:ToqLjIdvV3AZQa3K6e5m9hy/nsGaUByc2dWQlctB9iA=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20240930132144-b5e64e81e8d3 h1:6D2gGAwyQBElSrp3E+9lSr7k8gLuP3Aiy20rweLWeBw=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20240930132144-b5e64e81e8d3/go.mod h1:YeND+6FDA7OuFgDzYODN8kfPhXLCehcpxe4T9mdnpCY=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975 h1:4/BZkGObFWZf4cLbE2Vqg/1VTz67Q0AJ7LHspWLKJoQ=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250331083058-4563aec7a975/go.mod h1:FGdGvhI40Dq+CTQaSzK9evuve774cgOUdGfVO04OXkw=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36 h1:AjZ58JRw1ZieFH/SdsddF5BXtsDKt5kSrKNPWrzYz3Y=
github.com/grafana/prometheus-alertmanager v0.25.1-0.20250604130045-92c8f6389b36/go.mod h1:O/QP1BCm0HHIzbKvgMzqb5sSyH88rzkFk84F4TfJjBU=
github.com/grafana/pyroscope-go/godeltaprof v0.1.8/go.mod h1:2+l7K7twW49Ct4wFluZD3tZ6e0SjanjcUUBPVD/UuGU=
github.com/grafana/sqlds/v4 v4.2.4/go.mod h1:BQRjUG8rOqrBI4NAaeoWrIMuoNgfi8bdhCJ+5cgEfLU=
github.com/grafana/tail v0.0.0-20230510142333-77b18831edf0 h1:bjh0PVYSVVFxzINqPFYJmAmJNrWPgnVjuSdYJGHmtFU=
@@ -1277,6 +1374,7 @@ github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636 h1:aSISeOcal5irEhJd1M+IrApc0PdcN7e7Aj4yuEnOrfQ=
github.com/shurcooL/go v0.0.0-20200502201357-93f07166e636/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
@@ -1308,6 +1406,7 @@ github.com/tdewolff/minify/v2 v2.12.8 h1:Q2BqOTmlMjoutkuD/OPCnJUpIqrzT3nRPkw+q+K
github.com/tdewolff/minify/v2 v2.12.8/go.mod h1:YRgk7CC21LZnbuke2fmYnCTq+zhCgpb0yJACOTUNJ1E=
github.com/tdewolff/parse/v2 v2.6.7 h1:WrFllrqmzAcrKHzoYgMupqgUBIfBVOb0yscFzDf8bBg=
github.com/tdewolff/parse/v2 v2.6.7/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM=
github.com/testcontainers/testcontainers-go v0.35.0/go.mod h1:oEVBj5zrfJTrgjwONs1SsRbnBtH9OKl+IGl3UMcr2B4=
github.com/testcontainers/testcontainers-go/modules/azurite v0.35.0 h1:gUZ25e1DVE/0+ZZ0nupsIo+C1j7UNloN7Pkg3w6tceI=
github.com/testcontainers/testcontainers-go/modules/azurite v0.35.0/go.mod h1:2Fc67EpyOEexLAF99zhSuzu9H22zd83pkjxEHHTtHf4=
github.com/testcontainers/testcontainers-go/modules/mongodb v0.35.0 h1:i1Kh9fmXgHG9z3uzJv5Arz7pDKVaaNpLrqyd+0xhYMA=
@@ -1326,6 +1425,7 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tinylib/msgp v1.1.8 h1:FCXC1xanKO4I8plpHGH2P7koL/RzZs12l/+r7vakfm0=
github.com/tinylib/msgp v1.1.8/go.mod h1:qkpG+2ldGg4xRFmx+jfTvZPxfGFhi64BcnL9vkCm/Tw=
github.com/tjhop/slog-gokit v0.1.3/go.mod h1:Bbu5v2748qpAWH7k6gse/kw3076IJf6owJmh7yArmJs=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/trivago/tgo v1.0.7 h1:uaWH/XIy9aWYWpjm2CU3RpcqZXmX2ysQ9/Go+d9gyrM=
@@ -1409,6 +1509,7 @@ github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA=
github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0=
github.com/yuin/goldmark v1.7.13 h1:GPddIs617DnBLFFVJFgpo1aBfe/4xcvMc3SB5t/D0pA=
github.com/yuin/goldmark v1.7.13/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8=
github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
@@ -1761,6 +1862,7 @@ golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
@@ -1840,6 +1942,7 @@ golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
@@ -1854,6 +1957,7 @@ golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c
golang.org/x/tools v0.24.1/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=

View File

@@ -1,5 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"npmClient": "yarn",
"version": "12.3.0-pre"
"version": "12.4.0-pre"
}

View File

@@ -3,7 +3,7 @@
"license": "AGPL-3.0-only",
"private": true,
"name": "grafana",
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"repository": "github:grafana/grafana",
"scripts": {
"check-frontend-dev": "./scripts/check-frontend-dev.sh",
@@ -92,7 +92,7 @@
"@emotion/eslint-plugin": "11.12.0",
"@grafana/eslint-config": "8.2.0",
"@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules",
"@grafana/plugin-e2e": "2.1.7",
"@grafana/plugin-e2e": "^3.0.1",
"@grafana/test-utils": "workspace:*",
"@manypkg/get-packages": "^3.0.0",
"@npmcli/package-json": "^6.0.0",
@@ -278,7 +278,7 @@
"@glideapps/glide-data-grid": "^6.0.0",
"@grafana/alerting": "workspace:*",
"@grafana/api-clients": "workspace:*",
"@grafana/assistant": "0.1.0",
"@grafana/assistant": "0.1.1",
"@grafana/aws-sdk": "0.7.1",
"@grafana/azure-sdk": "0.0.8",
"@grafana/data": "workspace:*",

View File

@@ -2,7 +2,7 @@
"author": "Grafana Labs",
"license": "Apache-2.0",
"name": "@grafana/alerting",
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"description": "Grafana Alerting Library Build vertical integrations on top of the industry-leading alerting solution",
"keywords": [
"typescript",
@@ -94,7 +94,7 @@
"dependencies": {
"@emotion/css": "11.13.5",
"@faker-js/faker": "^9.8.0",
"@grafana/i18n": "12.3.0-pre",
"@grafana/i18n": "12.4.0-pre",
"@reduxjs/toolkit": "^2.9.0",
"fishery": "^2.3.1",
"lodash": "^4.17.21",

View File

@@ -3,7 +3,7 @@
"license": "Apache-2.0",
"private": true,
"name": "@grafana/api-clients",
"version": "12.3.0-pre",
"version": "12.4.0-pre",
"description": "Grafana API client utilities",
"keywords": [
"grafana",

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