From c74a5fcbedba9d0c4717ce0348065da3cb487d4f Mon Sep 17 00:00:00 2001 From: Will Browne Date: Tue, 11 Mar 2025 14:24:20 +0000 Subject: [PATCH 01/15] Chore: Avoid simplejson usage in `xorm` module (#101943) avoid simplejson usage --- pkg/util/xorm/go.mod | 2 -- pkg/util/xorm/go.sum | 4 ---- pkg/util/xorm/xorm_test.go | 7 +++---- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/pkg/util/xorm/go.mod b/pkg/util/xorm/go.mod index 310a1bbc263..fb55683da79 100644 --- a/pkg/util/xorm/go.mod +++ b/pkg/util/xorm/go.mod @@ -5,7 +5,6 @@ go 1.23.7 require ( cloud.google.com/go/spanner v1.75.0 github.com/googleapis/go-sql-spanner v1.11.1 - github.com/grafana/grafana v5.4.5+incompatible github.com/mattn/go-sqlite3 v1.14.22 github.com/stretchr/testify v1.10.0 xorm.io/builder v0.3.6 @@ -23,7 +22,6 @@ require ( cloud.google.com/go/monitoring v1.23.0 // indirect github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.2 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect - github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect diff --git a/pkg/util/xorm/go.sum b/pkg/util/xorm/go.sum index 7febbb40654..f93449ff49b 100644 --- a/pkg/util/xorm/go.sum +++ b/pkg/util/xorm/go.sum @@ -632,8 +632,6 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= github.com/apache/thrift v0.16.0/go.mod h1:PHK3hniurgQaNMZYaCLEqXKsYK8upmhPbmdP2FXSqgU= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -834,8 +832,6 @@ github.com/googleapis/go-sql-spanner v1.11.1 h1:z3ThtKV5HFvaNv9UGc26+ggS+lS0dsCA github.com/googleapis/go-sql-spanner v1.11.1/go.mod h1:fuA5q4yMS3SZiVfRr5bvksPNk7zUn/irbQW62H/ffZw= github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/grafana/grafana v5.4.5+incompatible h1:xNuhSBxLgwDwesuQIAhQu1QCk6tD0TAghKHE36/hxrs= -github.com/grafana/grafana v5.4.5+incompatible/go.mod h1:U8QyUclJHj254BFcuw45p6sg7eeGYX44qn1ShYo5rGE= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= github.com/grpc-ecosystem/grpc-gateway/v2 v2.11.3/go.mod h1:o//XUCC/F+yRGJoPO/VU0GSB0f8Nhgmxx0VIRUvaC0w= diff --git a/pkg/util/xorm/xorm_test.go b/pkg/util/xorm/xorm_test.go index 4dee7eb8294..8b8d5aeb03b 100644 --- a/pkg/util/xorm/xorm_test.go +++ b/pkg/util/xorm/xorm_test.go @@ -1,12 +1,11 @@ package xorm import ( + "encoding/json" "testing" _ "github.com/mattn/go-sqlite3" "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/components/simplejson" ) func TestBasicOperationsWithSqlite(t *testing.T) { @@ -38,7 +37,7 @@ func testBasicOperations(t *testing.T, eng *Engine) { require.NoError(t, err) require.NotZero(t, obj.Id) - obj.Json = simplejson.MustJson([]byte(`{"test": "test", "key": null}`)) + obj.Json = json.RawMessage(`{"test": "test", "key": null}`) _, err = sess.Update(obj) require.NoError(t, err) }) @@ -47,5 +46,5 @@ func testBasicOperations(t *testing.T, eng *Engine) { type TestStruct struct { Id int64 Comment string - Json *simplejson.Json + Json json.RawMessage } From d9cb6e632dfb9c36e9e72ce65959f260a60c69d5 Mon Sep 17 00:00:00 2001 From: Matthew Thorning Date: Tue, 11 Mar 2025 14:31:43 +0000 Subject: [PATCH 02/15] Navigation: Add the `IsNew` badge to the IRM menu item (#101926) add the `IsNew` badge to the IRM menu item --- pkg/services/navtree/navtreeimpl/applinks.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index 4c71eb150ed..c3a385fd67d 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -239,6 +239,9 @@ func (s *ServiceImpl) addPluginToSection(c *contextmodel.ReqContext, treeRoot *n alertsAndIncidentsChildren = append(alertsAndIncidentsChildren, alertingNode) treeRoot.RemoveSection(alertingNode) } + if appLink.Id == "plugin-page-grafana-irm-app" { + appLink.IsNew = true + } alertsAndIncidentsChildren = append(alertsAndIncidentsChildren, appLink) treeRoot.AddSection(&navtree.NavLink{ Text: "Alerts & IRM", From 82610288b1170c0c7565c8e4b9134b8f6593c1eb Mon Sep 17 00:00:00 2001 From: Yulia Shanyrova Date: Tue, 11 Mar 2025 15:51:25 +0100 Subject: [PATCH 03/15] Plugins: Move raiseanissueurl from plugin object to plugin details (#101428) * move raiseanissueurl from plugin object to plugin details * updated the test for PluginDetailsPane; --- public/app/features/plugins/admin/api.ts | 1 + .../components/PluginDetailsPanel.test.tsx | 32 +++++++++++++++++++ .../admin/components/PluginDetailsPanel.tsx | 10 ++++-- public/app/features/plugins/admin/helpers.ts | 5 --- public/app/features/plugins/admin/types.ts | 3 +- 5 files changed, 42 insertions(+), 9 deletions(-) diff --git a/public/app/features/plugins/admin/api.ts b/public/app/features/plugins/admin/api.ts index f79968d47ae..4520989603f 100644 --- a/public/app/features/plugins/admin/api.ts +++ b/public/app/features/plugins/admin/api.ts @@ -39,6 +39,7 @@ export async function getPluginDetails(id: string): Promise=9.0.0', statusContext: 'stable', @@ -118,4 +134,20 @@ describe('PluginDetailsPanel', () => { const panel = screen.getByTestId('plugin-details-panel'); expect(panel).toHaveStyle({ width: '300px' }); }); + + it('should render license, documentation, repository, raise issue links', () => { + render(); + const repositoryLink = screen.getByText('Repository'); + const licenseLink = screen.getByText('License'); + const documentationLink = screen.getByText('Documentation'); + const raiseIssueLink = screen.getByText('Raise issue'); + expect(repositoryLink).toBeInTheDocument(); + expect(repositoryLink).toHaveAttribute('href', 'https://github.com/grafana/test-plugin'); + expect(licenseLink).toBeInTheDocument(); + expect(licenseLink).toHaveAttribute('href', 'https://github.com/grafana/test-plugin/blob/main/LICENSE'); + expect(documentationLink).toBeInTheDocument(); + expect(documentationLink).toHaveAttribute('href', 'https://test-plugin.com/docs'); + expect(raiseIssueLink).toBeInTheDocument(); + expect(raiseIssueLink).toHaveAttribute('href', 'https://github.com/grafana/test-plugin/issues/new'); + }); }); diff --git a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx index 7d7612dc814..8440b37c76d 100644 --- a/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx +++ b/public/app/features/plugins/admin/components/PluginDetailsPanel.tsx @@ -99,8 +99,14 @@ export function PluginDetailsPanel(props: Props): React.ReactElement | null { Repository )} - {plugin.raiseAnIssueUrl && ( - + {plugin.details?.raiseAnIssueUrl && ( + Raise an issue )} diff --git a/public/app/features/plugins/admin/helpers.ts b/public/app/features/plugins/admin/helpers.ts index 28386a73cbd..8b0bfe80cfd 100644 --- a/public/app/features/plugins/admin/helpers.ts +++ b/public/app/features/plugins/admin/helpers.ts @@ -122,7 +122,6 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C versionSignatureType, versionSignedByOrgName, url, - raiseAnIssueUrl, } = plugin; const isDisabled = !!error || isDisabledSecretsPlugin(typeCode); @@ -161,7 +160,6 @@ export function mapRemoteToCatalog(plugin: RemotePlugin, error?: PluginError): C isFullyInstalled: isDisabled, latestVersion: plugin.version, url, - raiseAnIssueUrl, }; } @@ -178,7 +176,6 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat hasUpdate, accessControl, angularDetected, - raiseAnIssueUrl, } = plugin; const isDisabled = !!error || isDisabledSecretsPlugin(type); @@ -213,7 +210,6 @@ export function mapLocalToCatalog(plugin: LocalPlugin, error?: PluginError): Cat isFullyInstalled: true, iam: plugin.iam, latestVersion: plugin.latestVersion, - raiseAnIssueUrl, }; } @@ -278,7 +274,6 @@ export function mapToCatalogPlugin(local?: LocalPlugin, remote?: RemotePlugin, e iam: local?.iam, latestVersion: local?.latestVersion || remote?.version || '', url: remote?.url || '', - raiseAnIssueUrl: remote?.raiseAnIssueUrl || local?.raiseAnIssueUrl, }; } diff --git a/public/app/features/plugins/admin/types.ts b/public/app/features/plugins/admin/types.ts index 96e1d2451d2..4476c824216 100644 --- a/public/app/features/plugins/admin/types.ts +++ b/public/app/features/plugins/admin/types.ts @@ -65,7 +65,6 @@ export interface CatalogPlugin extends WithAccessControlMetadata { iam?: IdentityAccessManagement; isProvisioned?: boolean; url?: string; - raiseAnIssueUrl?: string; } export interface CatalogPluginDetails { @@ -83,6 +82,7 @@ export interface CatalogPluginDetails { lastCommitDate?: string; licenseUrl?: string; documentationUrl?: string; + raiseAnIssueUrl?: string; signatureType?: PluginSignatureType; signature?: PluginSignatureStatus; } @@ -197,7 +197,6 @@ export type LocalPlugin = WithAccessControlMetadata & { dependencies: PluginDependencies; angularDetected: boolean; iam?: IdentityAccessManagement; - raiseAnIssueUrl?: string; }; interface IdentityAccessManagement { From c8c17683ed9fdca0270086504db3258edd1b2998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Laura=20Fern=C3=A1ndez?= Date: Tue, 11 Mar 2025 15:55:30 +0100 Subject: [PATCH 04/15] ThemeDemo: Use `Combobox` instead of `Select` (#101947) --- packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx b/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx index ba7146bcf23..0e6315899df 100644 --- a/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx +++ b/packages/grafana-ui/src/components/ThemeDemos/ThemeDemo.tsx @@ -9,6 +9,7 @@ import { useTheme2 } from '../../themes/ThemeContext'; import { allButtonVariants, Button } from '../Button'; import { Card } from '../Card/Card'; import { CollapsableSection } from '../Collapse/CollapsableSection'; +import { Combobox } from '../Combobox/Combobox'; import { Field } from '../Forms/Field'; import { InlineField } from '../Forms/InlineField'; import { InlineFieldRow } from '../Forms/InlineFieldRow'; @@ -17,7 +18,6 @@ import { Icon } from '../Icon/Icon'; import { Input } from '../Input/Input'; import { BackgroundColor, BorderColor, Box, BoxShadow } from '../Layout/Box/Box'; import { Stack } from '../Layout/Stack/Stack'; -import { Select } from '../Select/Select'; import { Switch } from '../Switch/Switch'; import { Text, TextProps } from '../Text/Text'; @@ -150,8 +150,8 @@ export const ThemeDemo = () => { - - {}} /> + {}} /> ); From 5bfe046da957d32cfd57f0dd50d27c7eb28befef Mon Sep 17 00:00:00 2001 From: Pepe Cano <825430+ppcano@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:58:25 +0100 Subject: [PATCH 07/15] docs(alerting): clarify behaviour when provisioning the policy tree (#101937) --- .../export-alerting-resources/index.md | 6 +----- .../file-provisioning/index.md | 6 +----- .../terraform-provisioning/index.md | 6 +----- docs/sources/shared/alerts/alerting_provisioning.md | 2 ++ docs/sources/shared/alerts/warning-provisioning-tree.md | 9 +++++++++ 5 files changed, 14 insertions(+), 15 deletions(-) create mode 100644 docs/sources/shared/alerts/warning-provisioning-tree.md diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/export-alerting-resources/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/export-alerting-resources/index.md index 3a6f3576452..a7fac56ebe9 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/export-alerting-resources/index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/export-alerting-resources/index.md @@ -197,11 +197,7 @@ However, you can export it by manually copying the content and name of the notif All notification policies are provisioned through a single resource: the root of the notification policy tree. -{{% admonition type="warning" %}} - -Since the policy tree is a single resource, provisioning it overwrites a policy tree created through any other means. - -{{< /admonition >}} +{{< docs/shared lookup="alerts/warning-provisioning-tree.md" source="grafana" version="" >}} To export the notification policy tree from the Grafana UI, complete the following steps. diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md index 850600cc074..f7cb4a0e174 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/file-provisioning/index.md @@ -702,11 +702,7 @@ Create or reset the notification policy tree using provisioning files in your Gr In Grafana, the entire notification policy tree is considered a single, large resource. Add new specific policies as sub-policies under the root policy. Since specific policies may depend on each other, you cannot provision subsets of the policy tree; the entire tree must be defined in a single place. -{{% admonition type="warning" %}} - -Since the policy tree is a single resource, provisioning it will overwrite a policy tree created through any other means. - -{{< /admonition >}} +{{< docs/shared lookup="alerts/warning-provisioning-tree.md" source="grafana" version="" >}} 1. Find the notification policy tree in Grafana. 1. [Export](ref:export_policies) and download a provisioning file for your notification policy tree. diff --git a/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md b/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md index 0821f5ec0b4..2a5f5d97211 100644 --- a/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md +++ b/docs/sources/alerting/set-up/provision-alerting-resources/terraform-provisioning/index.md @@ -341,11 +341,7 @@ In this section, we'll create Terraform configurations for each alerting resourc [Notification policies](ref:notification-policy) defines how to route alert instances to your contact points. -{{% admonition type="warning" %}} - -Since the policy tree is a single resource, provisioning the `grafana_notification_policy` resource will overwrite a policy tree created through any other means. - -{{< /admonition >}} +{{< docs/shared lookup="alerts/warning-provisioning-tree.md" source="grafana" version="" >}} 1. Find the default notification policy tree. Alternatively, consider writing the resource in code as demonstrated in the example below. diff --git a/docs/sources/shared/alerts/alerting_provisioning.md b/docs/sources/shared/alerts/alerting_provisioning.md index 134bd086f97..40d3c72622d 100644 --- a/docs/sources/shared/alerts/alerting_provisioning.md +++ b/docs/sources/shared/alerts/alerting_provisioning.md @@ -1386,6 +1386,8 @@ Status: Conflict ### Sets the notification policy tree. (_RoutePutPolicyTree_) +{{< docs/shared lookup="alerts/warning-provisioning-tree.md" source="grafana" version="" >}} + ``` PUT /api/v1/provisioning/policies ``` diff --git a/docs/sources/shared/alerts/warning-provisioning-tree.md b/docs/sources/shared/alerts/warning-provisioning-tree.md new file mode 100644 index 00000000000..36ecf9b0fa6 --- /dev/null +++ b/docs/sources/shared/alerts/warning-provisioning-tree.md @@ -0,0 +1,9 @@ +--- +title: 'Warning Provisioning Tree' +--- + +{{% admonition type="warning" %}} + +Since the policy tree is a single resource, provisioning it will overwrite all policies in the notification policy tree. However, it does not affect internal policies created when alert rules directly select a contact point. + +{{< /admonition >}} From f6f6ae449615cfb866aed4afe53dc4a0cde83f25 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Tue, 11 Mar 2025 16:27:17 +0100 Subject: [PATCH 08/15] Zanzana: Update docs with subresources description (#101948) * Zanzana: Update docs with subresources description * clarify resource name --- pkg/services/authz/zanzana/schema/README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/pkg/services/authz/zanzana/schema/README.md b/pkg/services/authz/zanzana/schema/README.md index 4462e0f18e0..3a41e96a11a 100644 --- a/pkg/services/authz/zanzana/schema/README.md +++ b/pkg/services/authz/zanzana/schema/README.md @@ -5,8 +5,8 @@ Here's some notes about [OpenFGA authorization model](https://openfga.dev/docs/m ## GroupResource level permissions A relation to a group_resource object grants access to all objects of the GroupResource. -They take the form of `{ “user”: “user:1”, relation: “read”, object:”group_resource:dashboard.grafana.app/dashboard” }`. This -example would grant `user:1` access to all `dashboard.grafana.app/dashboard` in the namespace. +They take the form of `{ “user”: “user:1”, relation: “read”, object:”group_resource:dashboard.grafana.app/dashboards” }`. This +example would grant `user:1` access to all `dashboard.grafana.app/dashboards` in the namespace. ## Folder level permissions @@ -20,11 +20,19 @@ This context holds all GroupResources in a list e.g. `{ "group_resources": ["das ## Resource level permissions -Most of our resource should use the generic resource type. +Most of our resource should use the generic resource type. -To grant a user direct access to a specific resource we store `{ “user”: “user:1”, relation: “read”, object:”resource:dashboard.grafana.app/dashboard/” }` with additional context. +To grant a user direct access to a specific resource we store `{ “user”: “user:1”, relation: “read”, object:”resource:dashboard.grafana.app/dashboards/” }` with additional context. This context store the GroupResource. `{ "group_resource": "dashboard.grafana.app/dashboards" }`. This is required so we can filter them out for list requests. +## Subresources + +Subresources enable more granular permissions for the resources. Example might be access to public dashboards or access to dashboard settings. + +To grant a user access to the subresource of the specific resource we store following tuple: `{ “user”: “user:1”, relation: “read”, object:”resource:dashboard.grafana.app/dashboards//” }` with additional context `{ "group_resource": "dashboard.grafana.app/dashboards/" }` + +It's also possible to grant user access to all subresources for specific resource type. It can be done with following tuple: `{ “user”: “user:1”, relation: “read”, object:”resource:dashboard.grafana.app/dashboards/” }`. + ## Managed permissions In the RBAC model managed permissions stored as a special "managed" role permissions. OpenFGA model allows to assign permissions directly to users, so it produces following tuples: @@ -58,4 +66,3 @@ type folder ``` According to the schema, user can get `read` access to folder if it has `read` relation granted directly to the folder or its parent folders. - From 13d1f0259762a9fb212b252ee7463cae606b008f Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Tue, 11 Mar 2025 09:40:15 -0600 Subject: [PATCH 09/15] Frontend Sandbox: Do not perform authenticated queries for non authenticated users (#101946) * Do not perform authenticated queries for non authenticated users * Empty commit --- .../sandbox/sandbox_plugin_loader_registry.test.ts | 9 +++++++++ .../plugins/sandbox/sandbox_plugin_loader_registry.ts | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.test.ts b/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.test.ts index 8138baf6b6f..9e1f8a11b0d 100644 --- a/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.test.ts +++ b/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.test.ts @@ -1,5 +1,6 @@ import { PluginMeta, PluginSignatureStatus, PluginSignatureType } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { contextSrv } from 'app/core/services/context_srv'; import { getPluginDetails } from '../admin/api'; import { CatalogPluginDetails } from '../admin/types'; @@ -30,6 +31,7 @@ jest.mock('../admin/api', () => ({ const getPluginSettingsMock = jest.mocked(getPluginSettings); const getPluginDetailsMock = jest.mocked(getPluginDetails); +const mockContextSrv = jest.mocked(contextSrv); const fakePluginSettings: PluginMeta = { id: 'test-plugin', @@ -45,6 +47,7 @@ describe('Sandbox eligibility checks', () => { jest.clearAllMocks(); getPluginDetailsMock.mockReset(); getPluginSettingsMock.mockReset(); + mockContextSrv.isSignedIn = true; // restore default check setSandboxEnabledCheck(isPluginFrontendSandboxEnabled); @@ -63,6 +66,12 @@ describe('Sandbox eligibility checks', () => { expect(result).toBe(false); }); + test('isPluginFrontendSandboxEligible returns false for unsigned users', async () => { + mockContextSrv.isSignedIn = false; + const isEligible = await isPluginFrontendSandboxEligible({ pluginId: 'test-plugin' }); + expect(isEligible).toBe(false); + }); + test('shouldLoadPluginInFrontendSandbox returns false when feature toggle is off', async () => { config.featureToggles.pluginsFrontendSandbox = false; const result = await shouldLoadPluginInFrontendSandbox({ pluginId: 'test-plugin' }); diff --git a/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.ts b/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.ts index 2dbef3be1e1..6001734a905 100644 --- a/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.ts +++ b/public/app/features/plugins/sandbox/sandbox_plugin_loader_registry.ts @@ -1,5 +1,6 @@ import { PluginSignatureType } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { contextSrv } from 'app/core/core'; import { getPluginDetails } from '../admin/api'; import { getPluginSettings } from '../pluginSettings'; @@ -62,6 +63,10 @@ export async function isPluginFrontendSandboxEligible({ return false; } + if (!contextSrv.isSignedIn) { + return false; + } + // grafana signature and internal plugins are not allowed in the sandbox return isPluginSignatureEligibleForSandbox({ pluginId }); } From 59d87fe3f1c0dd59353e6176bf55bd4a3c37c0bf Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Tue, 11 Mar 2025 10:15:58 -0600 Subject: [PATCH 10/15] Unified Storage: Use match all query instead of wildcard for not-in requirement query (#101953) use match all query insteaed of wildcard --- pkg/storage/unified/search/bleve.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/storage/unified/search/bleve.go b/pkg/storage/unified/search/bleve.go index 630c9832315..322abff0516 100644 --- a/pkg/storage/unified/search/bleve.go +++ b/pkg/storage/unified/search/bleve.go @@ -803,7 +803,7 @@ func requirementQuery(req *resource.Requirement, prefix string) (query.Query, *r boolQuery.AddMustNot(mustNotQueries...) // must still have a value - notEmptyQuery := bleve.NewWildcardQuery("*") + notEmptyQuery := bleve.NewMatchAllQuery() boolQuery.AddMust(notEmptyQuery) return boolQuery, nil From 7e4beb2074ae23e083312218c1177af35c815d80 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 11 Mar 2025 12:40:44 -0400 Subject: [PATCH 11/15] Alerting: API to return deleted rules (#101429) --- pkg/services/ngalert/api/api_ruler.go | 20 ++++ pkg/services/ngalert/api/persist.go | 1 + pkg/services/ngalert/store/alert_rule.go | 34 +++++++ pkg/services/ngalert/store/alert_rule_test.go | 57 +++++++++++ pkg/services/ngalert/tests/fakes/rules.go | 13 +++ pkg/tests/api/alerting/api_ruler_test.go | 98 +++++++++++++++++++ pkg/tests/api/alerting/testing.go | 10 ++ 7 files changed, 233 insertions(+) diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index 2d88ae849e1..e5ece7154e3 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -261,6 +261,26 @@ func (srv RulerSrv) RouteGetRulesGroupConfig(c *contextmodel.ReqContext, namespa // RouteGetRulesConfig returns all alert rules that are available to the current user func (srv RulerSrv) RouteGetRulesConfig(c *contextmodel.ReqContext) response.Response { + if strings.ToLower(c.Query("deleted")) == "true" { + if !srv.featureManager.IsEnabledGlobally(featuremgmt.FlagAlertRuleRestore) { + return ErrResp(http.StatusBadRequest, errors.New("restore of deleted rules is not enabled"), "") + } + if !c.SignedInUser.HasRole(identity.RoleAdmin) { + return ErrResp(http.StatusForbidden, errors.New("only admins can get deleted rules"), "") + } + rules, err := srv.store.ListDeletedRules(c.Req.Context(), c.SignedInUser.GetOrgID()) + if err != nil { + return ErrResp(http.StatusInternalServerError, err, "failed to get deleted rules") + } + result := apimodels.NamespaceConfigResponse{} + if len(rules) > 0 { + result[""] = []apimodels.GettableRuleGroupConfig{ + toGettableRuleGroupConfig("", rules, map[string]ngmodels.Provenance{}, srv.resolveUserIdToNameFn(c.Req.Context())), + } + } + return response.JSON(http.StatusOK, result) + } + namespaceMap, err := srv.store.GetUserVisibleNamespaces(c.Req.Context(), c.SignedInUser.GetOrgID(), c.SignedInUser) if err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to get namespaces visible to the user") diff --git a/pkg/services/ngalert/api/persist.go b/pkg/services/ngalert/api/persist.go index b169be59c07..30b79b666dd 100644 --- a/pkg/services/ngalert/api/persist.go +++ b/pkg/services/ngalert/api/persist.go @@ -23,6 +23,7 @@ type RuleStore interface { GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAlertRuleByUIDQuery) (*ngmodels.AlertRule, error) GetAlertRulesGroupByRuleUID(ctx context.Context, query *ngmodels.GetAlertRulesGroupByRuleUIDQuery) ([]*ngmodels.AlertRule, error) ListAlertRules(ctx context.Context, query *ngmodels.ListAlertRulesQuery) (ngmodels.RulesGroup, error) + ListDeletedRules(ctx context.Context, orgID int64) ([]*ngmodels.AlertRule, error) // InsertAlertRules will insert all alert rules passed into the function // and return the map of uuid to id. diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index 47ab409c63a..d4fb0202a78 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -236,6 +236,40 @@ func (st DBstore) GetAlertRuleVersions(ctx context.Context, orgID int64, guid st return alertRules, nil } +// ListDeletedRules retrieves a list of deleted alert rules for the specified organization ID from the database. +// It ensures that only the latest version of each rule is included and filters out invalid or duplicated versions. +// Returns a slice of *models.AlertRule or an error if the operation fails. +func (st DBstore) ListDeletedRules(ctx context.Context, orgID int64) ([]*ngmodels.AlertRule, error) { + alertRules := make([]*ngmodels.AlertRule, 0) + err := st.SQLStore.WithDbSession(ctx, func(sess *db.Session) error { + // take only the latest versions of each rule by GUID + rows, err := sess.Table(alertRuleVersion{}).Where("rule_org_id = ? AND rule_uid = ''", orgID).Rows(alertRuleVersion{}) + if err != nil { + return err + } + // Deserialize each rule separately in case any of them contain invalid JSON. + for rows.Next() { + rule := new(alertRuleVersion) + err = rows.Scan(rule) + if err != nil { + st.Logger.Error("Invalid rule version found in DB store, ignoring it", "func", "GetAlertRuleVersions", "error", err) + continue + } + converted, err := alertRuleToModelsAlertRule(alertRuleVersionToAlertRule(*rule), st.Logger) + if err != nil { + st.Logger.Error("Invalid rule found in DB store, cannot convert, ignoring it", "func", "GetAlertRuleVersions", "error", err, "version_id", rule.ID) + continue + } + alertRules = append(alertRules, &converted) + } + return nil + }) + if err != nil { + return nil, err + } + return alertRules, nil +} + // GetRuleByID retrieves models.AlertRule by ID. // It returns models.ErrAlertRuleNotFound if no alert rule is found for the provided ID. func (st DBstore) GetRuleByID(ctx context.Context, query ngmodels.GetAlertRuleByIDQuery) (result *ngmodels.AlertRule, err error) { diff --git a/pkg/services/ngalert/store/alert_rule_test.go b/pkg/services/ngalert/store/alert_rule_test.go index 2b6d7d76971..c3e94e7e806 100644 --- a/pkg/services/ngalert/store/alert_rule_test.go +++ b/pkg/services/ngalert/store/alert_rule_test.go @@ -1954,6 +1954,63 @@ func TestIntegration_ListAlertRules(t *testing.T) { }) } +func TestIntegration_ListDeletedRules(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + cfg := setting.NewCfg() + cfg.UnifiedAlerting = setting.UnifiedAlertingSettings{ + BaseInterval: 1 * time.Second, + RuleVersionRecordLimit: -1, + } + sqlStore := db.InitTestDB(t) + folderService := setupFolderService(t, sqlStore, cfg, featuremgmt.WithFeatures()) + b := &fakeBus{} + store := createTestStore(sqlStore, folderService, &logtest.Fake{}, cfg.UnifiedAlerting, b) + store.FeatureToggles = featuremgmt.WithFeatures(featuremgmt.FlagAlertRuleRestore) + + orgID := int64(1) + gen := models.RuleGen + gen = gen.With(gen.WithIntervalMatching(store.Cfg.BaseInterval), gen.WithOrgID(orgID)) + + result, err := store.InsertAlertRules(context.Background(), &models.AlertingUserUID, []models.AlertRule{gen.Generate()}) + require.NoError(t, err) + rule, err := store.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{UID: result[0].UID}) + require.NoError(t, err) + + rule2 := models.CopyRule(rule, gen.WithTitle(util.GenerateShortUID())) + err = store.UpdateAlertRules(context.Background(), &models.AlertingUserUID, []models.UpdateRule{ + { + Existing: rule, + New: *rule2, + }, + }) + require.NoError(t, err) + rule2, err = store.GetAlertRuleByUID(context.Background(), &models.GetAlertRuleByUIDQuery{UID: result[0].UID}) + require.NoError(t, err) + + versions, err := store.GetAlertRuleVersions(context.Background(), orgID, rule.GUID) + require.NoError(t, err) + require.Len(t, versions, 2) + + t.Run("should not return if rule is not deleted", func(t *testing.T) { + list, err := store.ListDeletedRules(context.Background(), orgID) + require.NoError(t, err) + require.Empty(t, list) + }) + + err = store.DeleteAlertRulesByUID(context.Background(), orgID, &models.AlertingUserUID, rule.UID) + require.NoError(t, err) + + t.Run("should return the last deleted rule", func(t *testing.T) { + list, err := store.ListDeletedRules(context.Background(), orgID) + require.NoError(t, err) + require.Len(t, list, 1) + assert.Empty(t, list[0].UID) + assert.Empty(t, rule2.Diff(list[0], "ID", "UID", "DashboardUID", "PanelID")) + }) +} + func createTestStore( sqlStore db.DB, folderService folder.Service, diff --git a/pkg/services/ngalert/tests/fakes/rules.go b/pkg/services/ngalert/tests/fakes/rules.go index 10bc282e8a8..24d9959a0e2 100644 --- a/pkg/services/ngalert/tests/fakes/rules.go +++ b/pkg/services/ngalert/tests/fakes/rules.go @@ -24,6 +24,7 @@ type RuleStore struct { // OrgID -> RuleGroup -> Namespace -> Rules Rules map[int64][]*models.AlertRule History map[string][]*models.AlertRule + Deleted map[int64][]*models.AlertRule Hook func(cmd any) error // use Hook if you need to intercept some query and return an error RecordedOps []any Folders map[int64][]*folder.Folder @@ -460,3 +461,15 @@ func (f *RuleStore) GetAlertRuleVersions(_ context.Context, orgID int64, guid st return f.History[guid], nil } + +func (f *RuleStore) ListDeletedRules(_ context.Context, orgID int64) ([]*models.AlertRule, error) { + f.mtx.Lock() + defer f.mtx.Unlock() + defer func() { + f.RecordedOps = append(f.RecordedOps, GenericRecordedQuery{Name: "ListDeletedRules", Params: []any{orgID}}) + }() + if err := f.Hook(orgID); err != nil { + return nil, err + } + return f.Deleted[orgID], nil +} diff --git a/pkg/tests/api/alerting/api_ruler_test.go b/pkg/tests/api/alerting/api_ruler_test.go index 9f03a070f53..3cdf2d2a20f 100644 --- a/pkg/tests/api/alerting/api_ruler_test.go +++ b/pkg/tests/api/alerting/api_ruler_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io" + "maps" "math/rand" "net/http" "path" @@ -15,6 +16,7 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/google/uuid" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/prometheus/alertmanager/pkg/labels" @@ -4645,6 +4647,102 @@ func TestIntegrationRuleVersions(t *testing.T) { }) } +func TestIntegrationRuleSoftDelete(t *testing.T) { + testinfra.SQLiteIntegrationTest(t) + + // Setup Grafana and its Database + dir, p := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + DisableLegacyAlerting: true, + EnableUnifiedAlerting: true, + EnableQuota: true, + DisableAnonymous: true, + AppModeProduction: true, + EnableFeatureToggles: []string{featuremgmt.FlagAlertRuleRestore}, + }) + + grafanaListedAddr, env := testinfra.StartGrafanaEnv(t, dir, p) + + createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ + DefaultOrgRole: string(org.RoleAdmin), + Password: "admin", + Login: "admin", + }) + + createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ + DefaultOrgRole: string(org.RoleEditor), + Password: "password", + Login: "editor", + }) + + adminClient := newAlertingApiClient(grafanaListedAddr, "admin", "admin") + editorClient := newAlertingApiClient(grafanaListedAddr, "editor", "password") + + deleted, status, data := adminClient.GetDeletedRulesWithStatus(t) + requireStatusCode(t, http.StatusOK, status, data) + require.Emptyf(t, deleted, "Expected empty list of deleted rules, got %v", deleted) + + // Create the namespace we'll save our alerts to. + adminClient.CreateFolder(t, "folder1", "folder1") + + var group apimodels.RuleGroupConfigResponse + { // create rules and some history + postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1-post.json")) + require.NoError(t, err) + var group1 apimodels.PostableRuleGroupConfig + require.NoError(t, json.Unmarshal(postGroupRaw, &group1)) + + // Create rule under folder1 + response := adminClient.PostRulesGroup(t, "folder1", &group1) + require.NotEmptyf(t, response.Created, "Expected created to be set") + + // create some versions of the rule + for i := 0; i < 3; i++ { + groups, status := adminClient.GetRulesGroup(t, "folder1", group1.Name) + require.Equal(t, http.StatusAccepted, status) + group1 = convertGettableRuleGroupToPostable(groups.GettableRuleGroupConfig) + group1.Rules[0].Annotations[util.GenerateShortUID()] = util.GenerateShortUID() + _ = adminClient.PostRulesGroup(t, "folder1", &group1) + } + group, status = adminClient.GetRulesGroup(t, "folder1", group1.Name) + require.Equal(t, http.StatusAccepted, status) + } + + // deleting group by using editor user + status, body := editorClient.DeleteRulesGroup(t, "folder1", group.Name) + require.Equalf(t, http.StatusAccepted, status, "failed to delete group. Response: %s", body) + + t.Run("should see deleted rules", func(t *testing.T) { + rules, status, raw := adminClient.GetDeletedRulesWithStatus(t) + requireStatusCode(t, http.StatusOK, status, raw) + + require.Containsf(t, rules, "", "All rules should be in empty folder but got %v", slices.Collect(maps.Keys(rules))) + require.Lenf(t, rules[""], 1, "All deleted rules should be in single group but got %d", len(rules[""])) + require.Equalf(t, "", rules[""][0].Name, "All deleted rules should be in empty group but got %v", rules[""][0].Name) + + require.Len(t, rules[""][0].Rules, len(group.Rules)) + require.Empty(t, cmp.Diff(group.Rules, rules[""][0].Rules, cmpopts.IgnoreFields(apimodels.GettableGrafanaRule{}, "UID", "Version", "Updated", "UpdatedBy"))) + rule := rules[""][0].Rules[0] + require.Equalf(t, "editor", rule.GrafanaManagedAlert.UpdatedBy.Name, "Field 'UpdatedBy' should be set by editor but got %v ", rule.GrafanaManagedAlert.UpdatedBy) + }) + + t.Run("only admin should be able to see deleted rules", func(t *testing.T) { + t.Run("editor", func(t *testing.T) { + _, status, raw := editorClient.GetDeletedRulesWithStatus(t) + requireStatusCode(t, http.StatusForbidden, status, raw) + }) + t.Run("viewer", func(t *testing.T) { + createUser(t, env.SQLStore, env.Cfg, user.CreateUserCommand{ + DefaultOrgRole: string(org.RoleViewer), + Password: "password", + Login: "viewer", + }) + client := newAlertingApiClient(grafanaListedAddr, "viewer", "password") + _, status, raw := client.GetDeletedRulesWithStatus(t) + requireStatusCode(t, http.StatusForbidden, status, raw) + }) + }) +} + func newTestingRuleConfig(t *testing.T) apimodels.PostableRuleGroupConfig { interval, err := model.ParseDuration("1m") require.NoError(t, err) diff --git a/pkg/tests/api/alerting/testing.go b/pkg/tests/api/alerting/testing.go index f613d7a5547..25a25fd4e58 100644 --- a/pkg/tests/api/alerting/testing.go +++ b/pkg/tests/api/alerting/testing.go @@ -647,6 +647,16 @@ func (a apiClient) GetAllRulesWithStatus(t *testing.T) (apimodels.NamespaceConfi return result, resp.StatusCode, b } +func (a apiClient) GetDeletedRulesWithStatus(t *testing.T) (apimodels.NamespaceConfigResponse, int, string) { + t.Helper() + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules", a.url), nil) + require.NoError(t, err) + q := req.URL.Query() + q.Add("deleted", "true") + req.URL.RawQuery = q.Encode() + return sendRequestJSON[apimodels.NamespaceConfigResponse](t, req, http.StatusOK) +} + func (a apiClient) ExportRulesWithStatus(t *testing.T, params *apimodels.AlertRulesExportParameters) (int, string) { t.Helper() u, err := url.Parse(fmt.Sprintf("%s/api/ruler/grafana/api/v1/export/rules", a.url)) From 42ae2fb02695281956e3787f7dbaa0cbb58e6d08 Mon Sep 17 00:00:00 2001 From: Will Assis <35489495+gassiss@users.noreply.github.com> Date: Tue, 11 Mar 2025 13:56:34 -0300 Subject: [PATCH 12/15] fix(unified-storage): add missing dashboard legacy_id when in legacy read mode (#101944) * add missing dashboard legacy_id when in modes 0-2 --- .../dashboard/legacysearcher/search_client.go | 8 +++++- .../legacysearcher/search_client_test.go | 27 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client.go b/pkg/registry/apis/dashboard/legacysearcher/search_client.go index 1f98dc3a1e1..c80919971c6 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client.go @@ -213,6 +213,11 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour searchFields.Field(resource.SEARCH_FIELD_TITLE), searchFields.Field(resource.SEARCH_FIELD_FOLDER), searchFields.Field(resource.SEARCH_FIELD_TAGS), + { + Name: unisearch.DASHBOARD_LEGACY_ID, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Deprecated legacy id of the dashboard", + }, { Name: sortByField, Type: resource.ResourceTableColumnDefinition_INT64, @@ -270,7 +275,7 @@ func (c *DashboardSearchClient) Search(ctx context.Context, req *resource.Resour list.Results.Rows = append(list.Results.Rows, &resource.ResourceTableRow{ Key: getResourceKey(dashboard, req.Options.Key.Namespace), - Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), tags, []byte(strconv.FormatInt(dashboard.SortMeta, 10))}, + Cells: [][]byte{[]byte(dashboard.Title), []byte(dashboard.FolderUID), tags, []byte(strconv.FormatInt(dashboard.ID, 10)), []byte(strconv.FormatInt(dashboard.SortMeta, 10))}, }) } @@ -306,6 +311,7 @@ func formatQueryResult(res []dashboards.DashboardSearchProjection) []*dashboards hit, exists := hits[key] if !exists { hit = &dashboards.DashboardSearchProjection{ + ID: item.ID, UID: item.UID, Title: item.Title, FolderUID: item.FolderUID, diff --git a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go index 4b56d2e9b6f..ffbc973ba07 100644 --- a/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go +++ b/pkg/registry/apis/dashboard/legacysearcher/search_client_test.go @@ -43,8 +43,8 @@ func TestDashboardSearchClient_Search(t *testing.T) { Type: "dash-db", // should set type based off of key Sort: sorter, }).Return([]dashboards.DashboardSearchProjection{ - {UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Term: "term"}, - {UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2"}, + {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Term: "term"}, + {ID: 2, UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2"}, }, nil).Once() req := &resource.ResourceSearchRequest{ @@ -72,6 +72,11 @@ func TestDashboardSearchClient_Search(t *testing.T) { searchFields.Field(resource.SEARCH_FIELD_TITLE), searchFields.Field(resource.SEARCH_FIELD_FOLDER), searchFields.Field(resource.SEARCH_FIELD_TAGS), + { + Name: unisearch.DASHBOARD_LEGACY_ID, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Deprecated legacy id of the dashboard", + }, { Name: "", // sort by should be empty if title is what we sorted by Type: resource.ResourceTableColumnDefinition_INT64, @@ -88,6 +93,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { []byte("Test Dashboard"), []byte("folder1"), tags, + []byte("1"), []byte(strconv.FormatInt(0, 10)), }, }, @@ -101,6 +107,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { []byte("Test Dashboard2"), []byte("folder2"), emptyTags, + []byte("2"), []byte(strconv.FormatInt(0, 10)), }, }, @@ -120,7 +127,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { Type: "dash-db", Sort: sortOptionAsc, }).Return([]dashboards.DashboardSearchProjection{ - {UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50)}, + {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50)}, }, nil).Once() req := &resource.ResourceSearchRequest{ @@ -145,6 +152,11 @@ func TestDashboardSearchClient_Search(t *testing.T) { searchFields.Field(resource.SEARCH_FIELD_TITLE), searchFields.Field(resource.SEARCH_FIELD_FOLDER), searchFields.Field(resource.SEARCH_FIELD_TAGS), + { + Name: unisearch.DASHBOARD_LEGACY_ID, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Deprecated legacy id of the dashboard", + }, { Name: "views_total", Type: resource.ResourceTableColumnDefinition_INT64, @@ -161,6 +173,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { []byte("Test Dashboard"), []byte("folder"), emptyTags, + []byte("1"), []byte(strconv.FormatInt(50, 10)), }, }, @@ -180,7 +193,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { Type: "dash-db", Sort: sortOptionAsc, }).Return([]dashboards.DashboardSearchProjection{ - {UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2)}, + {ID: 1, UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2)}, }, nil).Once() req := &resource.ResourceSearchRequest{ @@ -205,6 +218,11 @@ func TestDashboardSearchClient_Search(t *testing.T) { searchFields.Field(resource.SEARCH_FIELD_TITLE), searchFields.Field(resource.SEARCH_FIELD_FOLDER), searchFields.Field(resource.SEARCH_FIELD_TAGS), + { + Name: unisearch.DASHBOARD_LEGACY_ID, + Type: resource.ResourceTableColumnDefinition_INT64, + Description: "Deprecated legacy id of the dashboard", + }, { Name: "errors_last_30_days", Type: resource.ResourceTableColumnDefinition_INT64, @@ -221,6 +239,7 @@ func TestDashboardSearchClient_Search(t *testing.T) { []byte("Test Dashboard"), []byte("folder"), emptyTags, + []byte("1"), []byte(strconv.FormatInt(2, 10)), }, }, From 7a3415148e579c102c0d0f171c0fb26fbe6ac58c Mon Sep 17 00:00:00 2001 From: Sam Jewell <2903904+samjewell@users.noreply.github.com> Date: Tue, 11 Mar 2025 17:14:33 +0000 Subject: [PATCH 13/15] SQL Expressions: Add cell-limit for input dataframes (#101700) * expr: Add row limit to SQL expressions Adds a configurable row limit to SQL expressions to prevent memory issues with large result sets. The limit is configured via the `sql_expression_row_limit` setting in the `[expressions]` section of grafana.ini, with a default of 100,000 rows. The limit is enforced by checking the total number of rows across all input tables before executing the SQL query. If the total exceeds the limit, the query fails with an error message indicating the limit was exceeded. * revert addition of newline * Switch to table-driven tests * Remove single-frame test-cases. We only need to test for the multi frame case. Single frame is a subset of the multi-frame case * Add helper function Simplify the way tests are set up and written * Support convention, that limit: 0 is no limit * Set the row-limit in one place only * Update default limit to 20k rows As per some discussion here: https://raintank-corp.slack.com/archives/C071A5XCFST/p1741611647001369?thread_ts=1740047619.804869&cid=C071A5XCFST * Test row-limit is applied from config Make sure we protect this from regressions This is perhaps a brittle test, somewhat coupled to the code here. But it's good enough to prevent regressions at least. * Add public documentation for the limit * Limit total number of cells instead of rows * Use named-return for totalRows As @kylebrandt requested during review of #101700 * Leave DF cells as zero values during limits tests When testing the cell limit we don't interact with the cell values at all, so we leave them at their zero values both to speed up tests, and to simplify and clarify that their values aren't used. * Set SQLCmd limit at object creation - don't mutate * Test that SQL node receives limit when built And that it receives it from the Grafana config * Improve TODO message for new Expression Parser * Fix failing test by always creating config on the Service --- .../setup-grafana/configure-grafana/_index.md | 4 + pkg/expr/graph.go | 2 +- pkg/expr/graph_test.go | 2 + pkg/expr/nodes.go | 4 +- pkg/expr/reader.go | 4 +- pkg/expr/service_test.go | 65 ++++++++ pkg/expr/sql_command.go | 38 ++++- pkg/expr/sql_command_test.go | 142 +++++++++++++++++- pkg/setting/setting.go | 4 + 9 files changed, 253 insertions(+), 12 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/_index.md b/docs/sources/setup-grafana/configure-grafana/_index.md index 5c7b92515ec..9f16b072e63 100644 --- a/docs/sources/setup-grafana/configure-grafana/_index.md +++ b/docs/sources/setup-grafana/configure-grafana/_index.md @@ -2753,6 +2753,10 @@ Set the default start of the week, valid values are: `saturday`, `sunday`, `mond Set this to `false` to disable expressions and hide them in the Grafana UI. Default is `true`. +#### `sql_expression_cell_limit` + +Set the maximum number of cells that can be passed to a SQL expression. Default is `100000`. + ### `[geomap]` This section controls the defaults settings for **Geomap Plugin**. diff --git a/pkg/expr/graph.go b/pkg/expr/graph.go index 6632a6b74c1..ae0ec6f9660 100644 --- a/pkg/expr/graph.go +++ b/pkg/expr/graph.go @@ -277,7 +277,7 @@ func (s *Service) buildGraph(req *Request) (*simple.DirectedGraph, error) { case TypeDatasourceNode: node, err = s.buildDSNode(dp, rn, req) case TypeCMDNode: - node, err = buildCMDNode(rn, s.features) + node, err = buildCMDNode(rn, s.features, s.cfg.SQLExpressionCellLimit) case TypeMLNode: if s.features.IsEnabledGlobally(featuremgmt.FlagMlExpressions) { node, err = s.buildMLNode(dp, rn, req) diff --git a/pkg/expr/graph_test.go b/pkg/expr/graph_test.go index fafca8f6876..dfa9f5f5b0a 100644 --- a/pkg/expr/graph_test.go +++ b/pkg/expr/graph_test.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/setting" ) func TestServicebuildPipeLine(t *testing.T) { @@ -234,6 +235,7 @@ func TestServicebuildPipeLine(t *testing.T) { } s := Service{ features: featuremgmt.WithFeatures(featuremgmt.FlagExpressionParser), + cfg: setting.NewCfg(), } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index 1159ef13d04..dea3b10e659 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -106,7 +106,7 @@ func (gn *CMDNode) Execute(ctx context.Context, now time.Time, vars mathexp.Vars return gn.Command.Execute(ctx, now, vars, s.tracer) } -func buildCMDNode(rn *rawNode, toggles featuremgmt.FeatureToggles) (*CMDNode, error) { +func buildCMDNode(rn *rawNode, toggles featuremgmt.FeatureToggles, sqlExpressionCellLimit int64) (*CMDNode, error) { commandType, err := GetExpressionCommandType(rn.Query) if err != nil { return nil, fmt.Errorf("invalid command type in expression '%v': %w", rn.RefID, err) @@ -163,7 +163,7 @@ func buildCMDNode(rn *rawNode, toggles featuremgmt.FeatureToggles) (*CMDNode, er case TypeThreshold: node.Command, err = UnmarshalThresholdCommand(rn, toggles) case TypeSQL: - node.Command, err = UnmarshalSQLCommand(rn) + node.Command, err = UnmarshalSQLCommand(rn, sqlExpressionCellLimit) default: return nil, fmt.Errorf("expression command type '%v' in expression '%v' not implemented", commandType, rn.RefID) } diff --git a/pkg/expr/reader.go b/pkg/expr/reader.go index ef18d9d8c2b..10a219f0692 100644 --- a/pkg/expr/reader.go +++ b/pkg/expr/reader.go @@ -134,7 +134,9 @@ func (h *ExpressionQueryReader) ReadQuery( err = iter.ReadVal(q) if err == nil { eq.Properties = q - eq.Command, err = NewSQLCommand(common.RefID, q.Expression) + // TODO: Cascade limit from Grafana config in this (new Expression Parser) branch of the code + cellLimit := 0 // zero means no limit + eq.Command, err = NewSQLCommand(common.RefID, q.Expression, int64(cellLimit)) } case QueryTypeThreshold: diff --git a/pkg/expr/service_test.go b/pkg/expr/service_test.go index 2fe6f6e1e9c..a343cdaf7b4 100644 --- a/pkg/expr/service_test.go +++ b/pkg/expr/service_test.go @@ -146,6 +146,71 @@ func TestDSQueryError(t *testing.T) { require.Equal(t, fp(42), res.Responses["C"].Frames[0].Fields[0].At(0)) } +func TestSQLExpressionCellLimitFromConfig(t *testing.T) { + tests := []struct { + name string + configCellLimit int64 + expectedLimit int64 + }{ + { + name: "should pass default cell limit (0) to SQL command", + configCellLimit: 0, + expectedLimit: 0, + }, + { + name: "should pass custom cell limit to SQL command", + configCellLimit: 5000, + expectedLimit: 5000, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Create a request with an SQL expression + sqlQuery := Query{ + RefID: "A", + DataSource: dataSourceModel(), + JSON: json.RawMessage(`{ "datasource": { "uid": "__expr__", "type": "__expr__"}, "type": "sql", "expression": "SELECT 1 AS n" }`), + TimeRange: AbsoluteTimeRange{ + From: time.Time{}, + To: time.Time{}, + }, + } + + queries := []Query{sqlQuery} + + // Create service with specified cell limit + cfg := setting.NewCfg() + cfg.ExpressionsEnabled = true + cfg.SQLExpressionCellLimit = tt.configCellLimit + + features := featuremgmt.WithFeatures(featuremgmt.FlagSqlExpressions) + + // Create service with our configured limit + s := &Service{ + cfg: cfg, + features: features, + converter: &ResultConverter{ + Features: features, + }, + } + + req := &Request{Queries: queries, User: &user.SignedInUser{}} + + // Build the pipeline + pipeline, err := s.BuildPipeline(req) + require.NoError(t, err) + + node := pipeline[0] + cmdNode := node.(*CMDNode) + sqlCmd := cmdNode.Command.(*SQLCommand) + + // Verify the SQL command has the correct limit + require.Equal(t, tt.expectedLimit, sqlCmd.limit, "SQL command has incorrect cell limit") + }) + } +} + func fp(f float64) *float64 { return &f } diff --git a/pkg/expr/sql_command.go b/pkg/expr/sql_command.go index 069d2a39e91..0b4d7ab698e 100644 --- a/pkg/expr/sql_command.go +++ b/pkg/expr/sql_command.go @@ -19,10 +19,11 @@ type SQLCommand struct { query string varsToQuery []string refID string + limit int64 } // NewSQLCommand creates a new SQLCommand. -func NewSQLCommand(refID, rawSQL string) (*SQLCommand, error) { +func NewSQLCommand(refID, rawSQL string, limit int64) (*SQLCommand, error) { if rawSQL == "" { return nil, errutil.BadRequest("sql-missing-query", errutil.WithPublicMessage("missing SQL query")) @@ -40,15 +41,17 @@ func NewSQLCommand(refID, rawSQL string) (*SQLCommand, error) { if tables != nil { logger.Debug("REF tables", "tables", tables, "sql", rawSQL) } + return &SQLCommand{ query: rawSQL, varsToQuery: tables, refID: refID, + limit: limit, }, nil } // UnmarshalSQLCommand creates a SQLCommand from Grafana's frontend query. -func UnmarshalSQLCommand(rn *rawNode) (*SQLCommand, error) { +func UnmarshalSQLCommand(rn *rawNode, limit int64) (*SQLCommand, error) { if rn.TimeRange == nil { logger.Error("time range must be specified for refID", "refID", rn.RefID) return nil, fmt.Errorf("time range must be specified for refID %s", rn.RefID) @@ -65,7 +68,7 @@ func UnmarshalSQLCommand(rn *rawNode) (*SQLCommand, error) { return nil, fmt.Errorf("expected sql expression to be type string, but got type %T", expressionRaw) } - return NewSQLCommand(rn.RefID, expression) + return NewSQLCommand(rn.RefID, expression, limit) } // NeedsVars returns the variable names (refIds) that are dependencies @@ -91,12 +94,23 @@ func (gr *SQLCommand) Execute(ctx context.Context, now time.Time, vars mathexp.V allFrames = append(allFrames, frames...) } - rsp := mathexp.Results{} - - db := sql.DB{} + totalCells := totalCells(allFrames) + // limit of 0 or less means no limit (following convention) + if gr.limit > 0 && totalCells > gr.limit { + return mathexp.Results{}, + fmt.Errorf( + "SQL expression: total cell count across all input tables exceeds limit of %d. Total cells: %d", + gr.limit, + totalCells, + ) + } logger.Debug("Executing query", "query", gr.query, "frames", len(allFrames)) + + db := sql.DB{} frame, err := db.QueryFrames(ctx, gr.refID, gr.query, allFrames) + + rsp := mathexp.Results{} if err != nil { logger.Error("Failed to query frames", "error", err.Error()) rsp.Error = err @@ -121,3 +135,15 @@ func (gr *SQLCommand) Execute(ctx context.Context, now time.Time, vars mathexp.V func (gr *SQLCommand) Type() string { return TypeSQL.String() } + +func totalCells(frames []*data.Frame) (total int64) { + for _, frame := range frames { + if frame != nil { + // Calculate cells as rows × columns + rows := int64(frame.Rows()) + cols := int64(len(frame.Fields)) + total += rows * cols + } + } + return +} diff --git a/pkg/expr/sql_command_test.go b/pkg/expr/sql_command_test.go index 3e0c5527721..07387a46612 100644 --- a/pkg/expr/sql_command_test.go +++ b/pkg/expr/sql_command_test.go @@ -1,13 +1,21 @@ package expr import ( + "context" + "fmt" + "net/http" "strings" "testing" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/expr/mathexp" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace" ) func TestNewCommand(t *testing.T) { - t.Skip() - cmd, err := NewSQLCommand("a", "select a from foo, bar") + cmd, err := NewSQLCommand("a", "select a from foo, bar", 0) if err != nil && strings.Contains(err.Error(), "feature is not enabled") { return } @@ -25,3 +33,133 @@ func TestNewCommand(t *testing.T) { return } } + +// Helper function for creating test data +func createFrameWithRowsAndCols(rows int, cols int) *data.Frame { + frame := data.NewFrame("dummy") + + for c := 0; c < cols; c++ { + values := make([]string, rows) + frame.Fields = append(frame.Fields, data.NewField(fmt.Sprintf("col%d", c), nil, values)) + } + + return frame +} + +func TestSQLCommandCellLimits(t *testing.T) { + tests := []struct { + name string + limit int64 + frames []*data.Frame + vars []string + expectError bool + errorContains string + }{ + { + name: "single (long) frame within cell limit", + limit: 10, + frames: []*data.Frame{ + createFrameWithRowsAndCols(10, 1), // 10 cells + }, + vars: []string{"foo"}, + }, + { + name: "single (wide) frame within cell limit", + limit: 10, + frames: []*data.Frame{ + createFrameWithRowsAndCols(1, 10), // 10 cells + }, + vars: []string{"foo"}, + }, + { + name: "multiple frames within cell limit", + limit: 12, + frames: []*data.Frame{ + createFrameWithRowsAndCols(2, 3), // 6 cells + createFrameWithRowsAndCols(2, 3), // 6 cells + }, + vars: []string{"foo", "bar"}, + }, + { + name: "single (long) frame exceeds cell limit", + limit: 9, + frames: []*data.Frame{ + createFrameWithRowsAndCols(10, 1), // 10 cells > 9 limit + }, + vars: []string{"foo"}, + expectError: true, + errorContains: "exceeds limit", + }, + { + name: "single (wide) frame exceeds cell limit", + limit: 9, + frames: []*data.Frame{ + createFrameWithRowsAndCols(1, 10), // 10 cells > 9 limit + }, + vars: []string{"foo"}, + expectError: true, + errorContains: "exceeds limit", + }, + { + name: "multiple frames exceed cell limit", + limit: 11, + frames: []*data.Frame{ + createFrameWithRowsAndCols(2, 3), // 6 cells + createFrameWithRowsAndCols(2, 3), // 6 cells + }, + vars: []string{"foo", "bar"}, + expectError: true, + errorContains: "exceeds limit", + }, + { + name: "limit of 0 means no limit: allow large frame", + limit: 0, + frames: []*data.Frame{ + createFrameWithRowsAndCols(200000, 1), // 200,000 cells + }, + vars: []string{"foo", "bar"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd, err := NewSQLCommand("a", "select a from foo, bar", tt.limit) + require.NoError(t, err, "Failed to create SQL command") + + vars := mathexp.Vars{} + + for i, frame := range tt.frames { + vars[tt.vars[i]] = mathexp.Results{ + Values: mathexp.Values{mathexp.TableData{Frame: frame}}, + } + } + + _, err = cmd.Execute(context.Background(), time.Now(), vars, &testTracer{}) + + if tt.expectError { + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorContains) + } else { + require.NoError(t, err) + } + }) + } +} + +type testTracer struct { + trace.Tracer +} + +func (t *testTracer) Start(ctx context.Context, name string, s ...trace.SpanStartOption) (context.Context, trace.Span) { + return ctx, &testSpan{} +} +func (t *testTracer) Inject(context.Context, http.Header, trace.Span) { + +} + +type testSpan struct { + trace.Span +} + +func (ts *testSpan) End(opt ...trace.SpanEndOption) { +} diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index cc1b10b9054..c5805d8520d 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -419,6 +419,9 @@ type Cfg struct { // ExpressionsEnabled specifies whether expressions are enabled. ExpressionsEnabled bool + // SQLExpressionCellLimit is the maximum number of cells (rows × columns, across all frames) that can be accepted by a SQL expression. + SQLExpressionCellLimit int64 + ImageUploadProvider string // LiveMaxConnections is a maximum number of WebSocket connections to @@ -780,6 +783,7 @@ func (cfg *Cfg) readAnnotationSettings() error { func (cfg *Cfg) readExpressionsSettings() { expressions := cfg.Raw.Section("expressions") cfg.ExpressionsEnabled = expressions.Key("enabled").MustBool(true) + cfg.SQLExpressionCellLimit = expressions.Key("sql_expression_cell_limit").MustInt64(100000) } type AnnotationCleanupSettings struct { From e645a7d8ff3f5b31d1e04a1efcbce10d027cdefd Mon Sep 17 00:00:00 2001 From: Denis Vodopianov Date: Tue, 11 Mar 2025 18:25:52 +0100 Subject: [PATCH 14/15] Chore: update golang version in .drone.yaml (#101894) --- .drone.yml | 206 ++++++++++++++++---------------- public/api-enterprise-spec.json | 82 +++++++++++-- public/api-merged.json | 52 +++++++- public/openapi3.json | 52 +++++++- scripts/drone/variables.star | 2 +- 5 files changed, 276 insertions(+), 118 deletions(-) diff --git a/.drone.yml b/.drone.yml index dbcc1662f84..6340d739fd6 100644 --- a/.drone.yml +++ b/.drone.yml @@ -25,7 +25,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - ./bin/build verify-drone @@ -75,7 +75,7 @@ steps: - go install github.com/bazelbuild/buildtools/buildifier@latest - buildifier --lint=warn -mode=check -r . depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: lint-starlark trigger: event: @@ -437,7 +437,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -446,21 +446,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend - commands: - apk add --update build-base @@ -469,7 +469,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend-integration trigger: event: @@ -524,7 +524,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - echo $(/usr/bin/github-app-external-token) > /github-app/token @@ -569,16 +569,16 @@ steps: - apk add --update make - make gen-go depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-openapi-spec trigger: event: @@ -655,7 +655,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -665,7 +665,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -674,7 +674,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - yarn install --immutable || yarn install --immutable @@ -712,7 +712,7 @@ steps: - /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64 -a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu -a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7 - -a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.7 --yarn-cache=$$YARN_CACHE_FOLDER + -a docker:grafana:linux/arm/v7:ubuntu --go-version=1.24.1 --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.21.3 --tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{ .version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD @@ -1110,7 +1110,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -1124,7 +1124,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1133,14 +1133,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -1161,7 +1161,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -1182,7 +1182,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -1198,7 +1198,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -1214,7 +1214,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -1230,7 +1230,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: remote-alertmanager-integration-tests trigger: event: @@ -1312,7 +1312,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue trigger: event: @@ -1433,7 +1433,7 @@ steps: && return 1; fi depends_on: - clone-enterprise - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: swagger-gen trigger: event: @@ -1538,7 +1538,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -1549,7 +1549,7 @@ steps: - CODEGEN_VERIFY=1 make gen-cue depends_on: - clone-enterprise - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1559,14 +1559,14 @@ steps: - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: - clone-enterprise - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - apk add --update build-base @@ -1574,7 +1574,7 @@ steps: - go test -v -run=^$ -benchmem -timeout=1h -count=8 -bench=. ${GO_PACKAGES} depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: sqlite-benchmark-integration-tests - commands: - apk add --update build-base @@ -1586,7 +1586,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: postgres-benchmark-integration-tests - commands: - apk add --update build-base @@ -1597,7 +1597,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: mysql-8.0-benchmark-integration-tests trigger: event: @@ -1669,7 +1669,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue trigger: branch: main @@ -1852,7 +1852,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -1861,21 +1861,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend - commands: - apk add --update build-base @@ -1884,7 +1884,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend-integration trigger: branch: main @@ -1929,22 +1929,22 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - apk add --update make - make gen-go depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-openapi-spec - commands: - ./bin/build verify-drone @@ -2076,7 +2076,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - '# It is required that code generated from Thema/CUE be committed and in sync @@ -2086,7 +2086,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -2095,7 +2095,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - yarn install --immutable || yarn install --immutable @@ -2132,7 +2132,7 @@ steps: - /src/grafana-build artifacts -a targz:grafana:linux/amd64 -a targz:grafana:linux/arm64 -a targz:grafana:linux/arm/v7 -a docker:grafana:linux/amd64 -a docker:grafana:linux/amd64:ubuntu -a docker:grafana:linux/arm64 -a docker:grafana:linux/arm64:ubuntu -a docker:grafana:linux/arm/v7 - -a docker:grafana:linux/arm/v7:ubuntu --go-version=1.23.7 --yarn-cache=$$YARN_CACHE_FOLDER + -a docker:grafana:linux/arm/v7:ubuntu --go-version=1.24.1 --yarn-cache=$$YARN_CACHE_FOLDER --build-id=$$DRONE_BUILD_NUMBER --ubuntu-base=ubuntu:22.04 --alpine-base=alpine:3.21.3 --tag-format='{{ .version_base }}-{{ .buildID }}-{{ .arch }}' --ubuntu-tag-format='{{ .version_base }}-{{ .buildID }}-ubuntu-{{ .arch }}' --verify='false' --grafana-dir=$$PWD @@ -2607,7 +2607,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -2621,7 +2621,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -2630,14 +2630,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -2658,7 +2658,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -2679,7 +2679,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -2695,7 +2695,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -2711,7 +2711,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -2727,7 +2727,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: remote-alertmanager-integration-tests trigger: branch: main @@ -2996,7 +2996,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -3005,21 +3005,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend - commands: - apk add --update build-base @@ -3028,7 +3028,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend-integration trigger: branch: @@ -3071,22 +3071,22 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - apk add --update make - make gen-go depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - go run scripts/modowners/modowners.go check go.mod - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-modfile - commands: - apk add --update make - make swagger-validate - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: validate-openapi-spec trigger: branch: @@ -3165,7 +3165,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - echo $DRONE_RUNNER_NAME @@ -3179,7 +3179,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -3188,14 +3188,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -3216,7 +3216,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -3237,7 +3237,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -3253,7 +3253,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -3269,7 +3269,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -3285,7 +3285,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: remote-alertmanager-integration-tests trigger: branch: @@ -3385,7 +3385,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - ./bin/build artifacts docker fetch --edition oss @@ -3517,7 +3517,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - ./bin/build artifacts docker fetch --edition oss @@ -3658,7 +3658,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - ./bin/build artifacts packages --artifacts-editions=oss --tag $${DRONE_TAG} --src-bucket @@ -3750,7 +3750,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - yarn install --immutable || yarn install --immutable @@ -3850,7 +3850,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - depends_on: - compile-build-cmd @@ -3947,7 +3947,7 @@ steps: depends_on: [] environment: CGO_ENABLED: 0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: compile-build-cmd - commands: - ./bin/build publish grafana-com --edition oss ${DRONE_TAG} @@ -4009,7 +4009,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4084,7 +4084,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4201,7 +4201,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4352,7 +4352,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -4361,21 +4361,21 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - apk add --update build-base shared-mime-info shared-mime-info-lang - go list -f '{{.Dir}}/...' -m | xargs go test -short -covermode=atomic -timeout=5m depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend - commands: - apk add --update build-base @@ -4384,7 +4384,7 @@ steps: | grep -o '\(.*\)/' | sort -u) depends_on: - wire-install - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: test-backend-integration trigger: cron: @@ -4438,7 +4438,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4582,7 +4582,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4689,7 +4689,7 @@ steps: - export GITHUB_TOKEN=$(cat /github-app/token) - dagger run --silent /src/grafana-build artifacts -a $${ARTIFACTS} --grafana-ref=$${GRAFANA_REF} --enterprise-ref=$${ENTERPRISE_REF} --grafana-repo=$${GRAFANA_REPO} --version=$${VERSION} - --go-version=1.23.7 + --go-version=1.24.1 depends_on: - github-app-generate-token environment: @@ -4710,7 +4710,7 @@ steps: from_secret: grafana_api_key GCP_KEY_BASE64: from_secret: gcp_key_base64 - GO_VERSION: 1.23.7 + GO_VERSION: 1.24.1 GPG_PASSPHRASE: from_secret: packages_gpg_passphrase GPG_PRIVATE_KEY: @@ -4848,7 +4848,7 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-cue depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-cue - commands: - '# It is required that generated jsonnet is committed and in sync with its inputs.' @@ -4857,14 +4857,14 @@ steps: - apk add --update make - CODEGEN_VERIFY=1 make gen-jsonnet depends_on: [] - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: verify-gen-jsonnet - commands: - apk add --update make - make gen-go depends_on: - verify-gen-cue - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: wire-install - commands: - dockerize -wait tcp://postgres:5432 -timeout 120s @@ -4885,7 +4885,7 @@ steps: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest POSTGRES_HOST: postgres - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: postgres-integration-tests - commands: - dockerize -wait tcp://mysql80:3306 -timeout 120s @@ -4906,7 +4906,7 @@ steps: environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql80 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: mysql-8.0-integration-tests - commands: - dockerize -wait tcp://redis:6379 -timeout 120s @@ -4922,7 +4922,7 @@ steps: - wait-for-redis environment: REDIS_URL: redis://redis:6379/0 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: redis-integration-tests - commands: - dockerize -wait tcp://memcached:11211 -timeout 120s @@ -4938,7 +4938,7 @@ steps: - wait-for-memcached environment: MEMCACHED_HOSTS: memcached:11211 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: memcached-integration-tests - commands: - dockerize -wait tcp://mimir_backend:8080 -timeout 120s @@ -4954,7 +4954,7 @@ steps: environment: AM_TENANT_ID: test AM_URL: http://mimir_backend:8080 - image: golang:1.23.7-alpine + image: golang:1.24.1-alpine name: remote-alertmanager-integration-tests trigger: event: @@ -5257,7 +5257,7 @@ steps: - commands: - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM docker:27-cli - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM alpine/git:2.40.1 - - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.23.7-alpine + - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM golang:1.24.1-alpine - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:22.11.0-alpine - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM node:22-bookworm - trivy --exit-code 0 --severity UNKNOWN,LOW,MEDIUM google/cloud-sdk:431.0.0 @@ -5295,7 +5295,7 @@ steps: - commands: - trivy --exit-code 1 --severity HIGH,CRITICAL docker:27-cli - trivy --exit-code 1 --severity HIGH,CRITICAL alpine/git:2.40.1 - - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.23.7-alpine + - trivy --exit-code 1 --severity HIGH,CRITICAL golang:1.24.1-alpine - trivy --exit-code 1 --severity HIGH,CRITICAL node:22.11.0-alpine - trivy --exit-code 1 --severity HIGH,CRITICAL node:22-bookworm - trivy --exit-code 1 --severity HIGH,CRITICAL google/cloud-sdk:431.0.0 @@ -5564,6 +5564,6 @@ kind: secret name: gcr_credentials --- kind: signature -hmac: 33f2e5615dfd7889899f9f8f16f7716190fa637fe98f1efd7e29607f8946be7d +hmac: f55fddb4c6faf30b232ae778ec6c022c9f3d32955879e8a764c715642712c5ea ... diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index 3faeab0aa9a..a036e242e30 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -2779,6 +2779,7 @@ } }, "AnnotationActions": { + "description": "+k8s:deepcopy-gen=true", "type": "object", "properties": { "canAdd": { @@ -2853,6 +2854,7 @@ } }, "AnnotationPermission": { + "description": "+k8s:deepcopy-gen=true", "type": "object", "properties": { "dashboard": { @@ -3206,6 +3208,24 @@ "type": "string" } }, + "InhibitAnyPolicy": { + "description": "InhibitAnyPolicy and InhibitAnyPolicyZero indicate the presence and value\nof the inhibitAnyPolicy extension.\n\nThe value of InhibitAnyPolicy indicates the number of additional\ncertificates in the path after this certificate that may use the\nanyPolicy policy OID to indicate a match with any other policy.\n\nWhen parsing a certificate, a positive non-zero InhibitAnyPolicy means\nthat the field was specified, -1 means it was unset, and\nInhibitAnyPolicyZero being true mean that the field was explicitly set to\nzero. The case of InhibitAnyPolicy==0 with InhibitAnyPolicyZero==false\nshould be treated equivalent to -1 (unset).", + "type": "integer", + "format": "int64" + }, + "InhibitAnyPolicyZero": { + "description": "InhibitAnyPolicyZero indicates that InhibitAnyPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, + "InhibitPolicyMapping": { + "description": "InhibitPolicyMapping and InhibitPolicyMappingZero indicate the presence\nand value of the inhibitPolicyMapping field of the policyConstraints\nextension.\n\nThe value of InhibitPolicyMapping indicates the number of additional\ncertificates in the path after this certificate that may use policy\nmapping.\n\nWhen parsing a certificate, a positive non-zero InhibitPolicyMapping\nmeans that the field was specified, -1 means it was unset, and\nInhibitPolicyMappingZero being true mean that the field was explicitly\nset to zero. The case of InhibitPolicyMapping==0 with\nInhibitPolicyMappingZero==false should be treated equivalent to -1\n(unset).", + "type": "integer", + "format": "int64" + }, + "InhibitPolicyMappingZero": { + "description": "InhibitPolicyMappingZero indicates that InhibitPolicyMapping==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "IsCA": { "type": "boolean" }, @@ -3270,19 +3290,26 @@ } }, "Policies": { - "description": "Policies contains all policy identifiers included in the certificate.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", + "description": "Policies contains all policy identifiers included in the certificate.\nSee CreateCertificate for context about how this field and the PolicyIdentifiers field\ninteract.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", "type": "array", "items": { "type": "string" } }, "PolicyIdentifiers": { - "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.", + "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.\nSee CreateCertificate for context about how this field and the Policies field\ninteract.", "type": "array", "items": { "$ref": "#/definitions/ObjectIdentifier" } }, + "PolicyMappings": { + "description": "PolicyMappings contains a list of policy mappings included in the certificate.", + "type": "array", + "items": { + "$ref": "#/definitions/PolicyMapping" + } + }, "PublicKey": {}, "PublicKeyAlgorithm": { "$ref": "#/definitions/PublicKeyAlgorithm" @@ -3322,6 +3349,15 @@ "format": "uint8" } }, + "RequireExplicitPolicy": { + "description": "RequireExplicitPolicy and RequireExplicitPolicyZero indicate the presence\nand value of the requireExplicitPolicy field of the policyConstraints\nextension.\n\nThe value of RequireExplicitPolicy indicates the number of additional\ncertificates in the path after this certificate before an explicit policy\nis required for the rest of the path. When an explicit policy is required,\neach subsequent certificate in the path must contain a required policy OID,\nor a policy OID which has been declared as equivalent through the policy\nmapping extension.\n\nWhen parsing a certificate, a positive non-zero RequireExplicitPolicy\nmeans that the field was specified, -1 means it was unset, and\nRequireExplicitPolicyZero being true mean that the field was explicitly\nset to zero. The case of RequireExplicitPolicy==0 with\nRequireExplicitPolicyZero==false should be treated equivalent to -1\n(unset).", + "type": "integer", + "format": "int64" + }, + "RequireExplicitPolicyZero": { + "description": "RequireExplicitPolicyZero indicates that RequireExplicitPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "SerialNumber": { "type": "string" }, @@ -4047,6 +4083,9 @@ "annotationsPermissions": { "$ref": "#/definitions/AnnotationPermission" }, + "apiVersion": { + "type": "string" + }, "canAdmin": { "type": "boolean" }, @@ -4737,6 +4776,9 @@ "type": "integer", "format": "int64" }, + "managedBy": { + "$ref": "#/definitions/ManagerKind" + }, "orgId": { "type": "integer", "format": "int64" @@ -4752,10 +4794,6 @@ "$ref": "#/definitions/Folder" } }, - "repository": { - "description": "When the folder belongs to a repository\nNOTE: this is only populated when folders are managed by unified storage", - "type": "string" - }, "title": { "type": "string" }, @@ -4785,11 +4823,10 @@ "type": "integer", "format": "int64" }, - "parentUid": { - "type": "string" + "managedBy": { + "$ref": "#/definitions/ManagerKind" }, - "repository": { - "description": "When the folder belongs to a repository\nNOTE: this is only populated when folders are managed by unified storage", + "parentUid": { "type": "string" }, "title": { @@ -5536,6 +5573,11 @@ } } }, + "ManagerKind": { + "description": "It can be a user or a tool or a generic API client.\n+enum", + "type": "string", + "title": "ManagerKind is the type of manager, which is responsible for managing the resource." + }, "MassDeleteAnnotationsCmd": { "type": "object", "properties": { @@ -6175,6 +6217,20 @@ "$ref": "#/definitions/Playlist" } }, + "PolicyMapping": { + "type": "object", + "title": "PolicyMapping represents a policy mapping entry in the policyMappings extension.", + "properties": { + "IssuerDomainPolicy": { + "description": "IssuerDomainPolicy contains a policy OID the issuing certificate considers\nequivalent to SubjectDomainPolicy in the subject certificate.", + "type": "string" + }, + "SubjectDomainPolicy": { + "description": "SubjectDomainPolicy contains a OID the issuing certificate considers\nequivalent to IssuerDomainPolicy in the subject certificate.", + "type": "string" + } + } + }, "PostAnnotationsCmd": { "type": "object", "required": [ @@ -9946,6 +10002,12 @@ "type": "object" } }, + "notAcceptableError": { + "description": "NotAcceptableError is returned when the server cannot produce a response matching the accepted formats.", + "schema": { + "$ref": "#/definitions/ErrorResponseBody" + } + }, "notFoundError": { "description": "NotFoundError is returned when the requested resource was not found.", "schema": { diff --git a/public/api-merged.json b/public/api-merged.json index 873200a1fb7..44d88382eb6 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -13615,6 +13615,24 @@ "type": "string" } }, + "InhibitAnyPolicy": { + "description": "InhibitAnyPolicy and InhibitAnyPolicyZero indicate the presence and value\nof the inhibitAnyPolicy extension.\n\nThe value of InhibitAnyPolicy indicates the number of additional\ncertificates in the path after this certificate that may use the\nanyPolicy policy OID to indicate a match with any other policy.\n\nWhen parsing a certificate, a positive non-zero InhibitAnyPolicy means\nthat the field was specified, -1 means it was unset, and\nInhibitAnyPolicyZero being true mean that the field was explicitly set to\nzero. The case of InhibitAnyPolicy==0 with InhibitAnyPolicyZero==false\nshould be treated equivalent to -1 (unset).", + "type": "integer", + "format": "int64" + }, + "InhibitAnyPolicyZero": { + "description": "InhibitAnyPolicyZero indicates that InhibitAnyPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, + "InhibitPolicyMapping": { + "description": "InhibitPolicyMapping and InhibitPolicyMappingZero indicate the presence\nand value of the inhibitPolicyMapping field of the policyConstraints\nextension.\n\nThe value of InhibitPolicyMapping indicates the number of additional\ncertificates in the path after this certificate that may use policy\nmapping.\n\nWhen parsing a certificate, a positive non-zero InhibitPolicyMapping\nmeans that the field was specified, -1 means it was unset, and\nInhibitPolicyMappingZero being true mean that the field was explicitly\nset to zero. The case of InhibitPolicyMapping==0 with\nInhibitPolicyMappingZero==false should be treated equivalent to -1\n(unset).", + "type": "integer", + "format": "int64" + }, + "InhibitPolicyMappingZero": { + "description": "InhibitPolicyMappingZero indicates that InhibitPolicyMapping==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "IsCA": { "type": "boolean" }, @@ -13679,19 +13697,26 @@ } }, "Policies": { - "description": "Policies contains all policy identifiers included in the certificate.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", + "description": "Policies contains all policy identifiers included in the certificate.\nSee CreateCertificate for context about how this field and the PolicyIdentifiers field\ninteract.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", "type": "array", "items": { "type": "string" } }, "PolicyIdentifiers": { - "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.", + "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.\nSee CreateCertificate for context about how this field and the Policies field\ninteract.", "type": "array", "items": { "$ref": "#/definitions/ObjectIdentifier" } }, + "PolicyMappings": { + "description": "PolicyMappings contains a list of policy mappings included in the certificate.", + "type": "array", + "items": { + "$ref": "#/definitions/PolicyMapping" + } + }, "PublicKey": {}, "PublicKeyAlgorithm": { "$ref": "#/definitions/PublicKeyAlgorithm" @@ -13731,6 +13756,15 @@ "format": "uint8" } }, + "RequireExplicitPolicy": { + "description": "RequireExplicitPolicy and RequireExplicitPolicyZero indicate the presence\nand value of the requireExplicitPolicy field of the policyConstraints\nextension.\n\nThe value of RequireExplicitPolicy indicates the number of additional\ncertificates in the path after this certificate before an explicit policy\nis required for the rest of the path. When an explicit policy is required,\neach subsequent certificate in the path must contain a required policy OID,\nor a policy OID which has been declared as equivalent through the policy\nmapping extension.\n\nWhen parsing a certificate, a positive non-zero RequireExplicitPolicy\nmeans that the field was specified, -1 means it was unset, and\nRequireExplicitPolicyZero being true mean that the field was explicitly\nset to zero. The case of RequireExplicitPolicy==0 with\nRequireExplicitPolicyZero==false should be treated equivalent to -1\n(unset).", + "type": "integer", + "format": "int64" + }, + "RequireExplicitPolicyZero": { + "description": "RequireExplicitPolicyZero indicates that RequireExplicitPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "SerialNumber": { "type": "string" }, @@ -18116,6 +18150,20 @@ "$ref": "#/definitions/Playlist" } }, + "PolicyMapping": { + "type": "object", + "title": "PolicyMapping represents a policy mapping entry in the policyMappings extension.", + "properties": { + "IssuerDomainPolicy": { + "description": "IssuerDomainPolicy contains a policy OID the issuing certificate considers\nequivalent to SubjectDomainPolicy in the subject certificate.", + "type": "string" + }, + "SubjectDomainPolicy": { + "description": "SubjectDomainPolicy contains a OID the issuing certificate considers\nequivalent to IssuerDomainPolicy in the subject certificate.", + "type": "string" + } + } + }, "PostAnnotationsCmd": { "type": "object", "required": [ diff --git a/public/openapi3.json b/public/openapi3.json index 9d4c63135c3..ccfd199e14e 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -3676,6 +3676,24 @@ }, "type": "array" }, + "InhibitAnyPolicy": { + "description": "InhibitAnyPolicy and InhibitAnyPolicyZero indicate the presence and value\nof the inhibitAnyPolicy extension.\n\nThe value of InhibitAnyPolicy indicates the number of additional\ncertificates in the path after this certificate that may use the\nanyPolicy policy OID to indicate a match with any other policy.\n\nWhen parsing a certificate, a positive non-zero InhibitAnyPolicy means\nthat the field was specified, -1 means it was unset, and\nInhibitAnyPolicyZero being true mean that the field was explicitly set to\nzero. The case of InhibitAnyPolicy==0 with InhibitAnyPolicyZero==false\nshould be treated equivalent to -1 (unset).", + "format": "int64", + "type": "integer" + }, + "InhibitAnyPolicyZero": { + "description": "InhibitAnyPolicyZero indicates that InhibitAnyPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, + "InhibitPolicyMapping": { + "description": "InhibitPolicyMapping and InhibitPolicyMappingZero indicate the presence\nand value of the inhibitPolicyMapping field of the policyConstraints\nextension.\n\nThe value of InhibitPolicyMapping indicates the number of additional\ncertificates in the path after this certificate that may use policy\nmapping.\n\nWhen parsing a certificate, a positive non-zero InhibitPolicyMapping\nmeans that the field was specified, -1 means it was unset, and\nInhibitPolicyMappingZero being true mean that the field was explicitly\nset to zero. The case of InhibitPolicyMapping==0 with\nInhibitPolicyMappingZero==false should be treated equivalent to -1\n(unset).", + "format": "int64", + "type": "integer" + }, + "InhibitPolicyMappingZero": { + "description": "InhibitPolicyMappingZero indicates that InhibitPolicyMapping==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "IsCA": { "type": "boolean" }, @@ -3740,19 +3758,26 @@ "type": "array" }, "Policies": { - "description": "Policies contains all policy identifiers included in the certificate.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", + "description": "Policies contains all policy identifiers included in the certificate.\nSee CreateCertificate for context about how this field and the PolicyIdentifiers field\ninteract.\nIn Go 1.22, encoding/gob cannot handle and ignores this field.", "items": { "type": "string" }, "type": "array" }, "PolicyIdentifiers": { - "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.", + "description": "PolicyIdentifiers contains asn1.ObjectIdentifiers, the components\nof which are limited to int32. If a certificate contains a policy which\ncannot be represented by asn1.ObjectIdentifier, it will not be included in\nPolicyIdentifiers, but will be present in Policies, which contains all parsed\npolicy OIDs.\nSee CreateCertificate for context about how this field and the Policies field\ninteract.", "items": { "$ref": "#/components/schemas/ObjectIdentifier" }, "type": "array" }, + "PolicyMappings": { + "description": "PolicyMappings contains a list of policy mappings included in the certificate.", + "items": { + "$ref": "#/components/schemas/PolicyMapping" + }, + "type": "array" + }, "PublicKey": {}, "PublicKeyAlgorithm": { "$ref": "#/components/schemas/PublicKeyAlgorithm" @@ -3792,6 +3817,15 @@ }, "type": "array" }, + "RequireExplicitPolicy": { + "description": "RequireExplicitPolicy and RequireExplicitPolicyZero indicate the presence\nand value of the requireExplicitPolicy field of the policyConstraints\nextension.\n\nThe value of RequireExplicitPolicy indicates the number of additional\ncertificates in the path after this certificate before an explicit policy\nis required for the rest of the path. When an explicit policy is required,\neach subsequent certificate in the path must contain a required policy OID,\nor a policy OID which has been declared as equivalent through the policy\nmapping extension.\n\nWhen parsing a certificate, a positive non-zero RequireExplicitPolicy\nmeans that the field was specified, -1 means it was unset, and\nRequireExplicitPolicyZero being true mean that the field was explicitly\nset to zero. The case of RequireExplicitPolicy==0 with\nRequireExplicitPolicyZero==false should be treated equivalent to -1\n(unset).", + "format": "int64", + "type": "integer" + }, + "RequireExplicitPolicyZero": { + "description": "RequireExplicitPolicyZero indicates that RequireExplicitPolicy==0 should be\ninterpreted as an actual maximum path length of zero. Otherwise, that\ncombination is interpreted as InhibitAnyPolicy not being set.", + "type": "boolean" + }, "SerialNumber": { "type": "string" }, @@ -8179,6 +8213,20 @@ }, "type": "array" }, + "PolicyMapping": { + "properties": { + "IssuerDomainPolicy": { + "description": "IssuerDomainPolicy contains a policy OID the issuing certificate considers\nequivalent to SubjectDomainPolicy in the subject certificate.", + "type": "string" + }, + "SubjectDomainPolicy": { + "description": "SubjectDomainPolicy contains a OID the issuing certificate considers\nequivalent to IssuerDomainPolicy in the subject certificate.", + "type": "string" + } + }, + "title": "PolicyMapping represents a policy mapping entry in the policyMappings extension.", + "type": "object" + }, "PostAnnotationsCmd": { "properties": { "dashboardId": { diff --git a/scripts/drone/variables.star b/scripts/drone/variables.star index c737ef65d61..51da1d676ba 100644 --- a/scripts/drone/variables.star +++ b/scripts/drone/variables.star @@ -3,7 +3,7 @@ global variables """ grabpl_version = "v3.1.2" -golang_version = "1.23.7" +golang_version = "1.24.1" # nodejs_version should match what's in ".nvmrc", but without the v prefix. nodejs_version = "22.11.0" From 4dbd1846c70c839fa71a06b7fb72d82d784aeb14 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Tue, 11 Mar 2025 17:28:36 +0000 Subject: [PATCH 15/15] Chore: bump codeql versions used in pr checks (#101957) * bump codeql versions used in pr checks * update supported versions * use glob syntax * wider glob --- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/pr-codeql-analysis-go.yml | 4 ++-- .github/workflows/pr-codeql-analysis-javascript.yml | 4 ++-- .github/workflows/pr-codeql-analysis-python.yml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index c1f90ceb831..8c8b1abde50 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -8,7 +8,7 @@ name: "CodeQL" on: workflow_dispatch: push: - branches: [main, v1.8.x, v2.0.x, v2.1.x, v2.6.x, v3.0.x, v3.1.x, v4.0.x, v4.1.x, v4.2.x, v4.3.x, v4.4.x, v4.5.x, v4.6.x, v4.7.x, v5.0.x, v5.1.x, v5.2.x, v5.3.x, v5.4.x, v6.0.x, v6.1.x, v6.2.x, v6.3.x, v6.4.x, v6.5.x, v6.6.x, v6.7.x, v7.0.x, v7.1.x, v7.2.x] + branches: [main, v*.*.*] paths-ignore: - '**/*.cue' - '**/*.json' diff --git a/.github/workflows/pr-codeql-analysis-go.yml b/.github/workflows/pr-codeql-analysis-go.yml index ce9082f4400..46645b7fa3f 100644 --- a/.github/workflows/pr-codeql-analysis-go.yml +++ b/.github/workflows/pr-codeql-analysis-go.yml @@ -40,7 +40,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: "go" @@ -50,4 +50,4 @@ jobs: make build-go - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/pr-codeql-analysis-javascript.yml b/.github/workflows/pr-codeql-analysis-javascript.yml index 6c5264c926a..d24b7db9671 100644 --- a/.github/workflows/pr-codeql-analysis-javascript.yml +++ b/.github/workflows/pr-codeql-analysis-javascript.yml @@ -28,9 +28,9 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: "javascript" - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/pr-codeql-analysis-python.yml b/.github/workflows/pr-codeql-analysis-python.yml index aea55365afc..4e8b1b14747 100644 --- a/.github/workflows/pr-codeql-analysis-python.yml +++ b/.github/workflows/pr-codeql-analysis-python.yml @@ -26,9 +26,9 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: "python" - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3