Merge remote-tracking branch 'origin/main' into ds-apiserver-schema-builder

This commit is contained in:
Ryan McKinley
2025-12-18 10:31:57 +03:00
197 changed files with 6865 additions and 6003 deletions
@@ -0,0 +1,129 @@
package templateGroup
import (
"context"
"embed"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.yaml.in/yaml/v3"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1"
"github.com/grafana/grafana/pkg/services/featuremgmt"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/tests/api/alerting"
"github.com/grafana/grafana/pkg/tests/apis"
"github.com/grafana/grafana/pkg/tests/apis/alerting/notifications/common"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/util/testutil"
)
//go:embed test-data/*.*
var testData embed.FS
func TestIntegrationImportedTemplates(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
EnableFeatureToggles: []string{
featuremgmt.FlagAlertingImportAlertmanagerAPI,
},
})
client := common.NewTemplateGroupClient(t, helper.Org1.Admin)
cliCfg := helper.Org1.Admin.NewRestConfig()
alertingApi := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
configYaml, err := testData.ReadFile(path.Join("test-data", "imported.yaml"))
require.NoError(t, err)
identifier := "test-create-get-config"
mergeMatchers := "_imported=true"
headers := map[string]string{
"Content-Type": "application/yaml",
"X-Grafana-Alerting-Config-Identifier": identifier,
"X-Grafana-Alerting-Merge-Matchers": mergeMatchers,
}
var amConfig apimodels.AlertmanagerUserConfig
require.NoError(t, yaml.Unmarshal(configYaml, &amConfig))
response := alertingApi.ConvertPrometheusPostAlertmanagerConfig(t, amConfig, headers)
require.Equal(t, "success", response.Status)
templates, err := client.List(context.Background(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, templates.Items, 3)
require.Equal(t, v0alpha1.DefaultTemplateTitle, templates.Items[0].Spec.Title)
require.Equal(t, "imported", templates.Items[1].Spec.Title)
require.Equal(t, "template", templates.Items[2].Spec.Title)
t.Run("should be correct kind", func(t *testing.T) {
assert.Equal(t,
v0alpha1.TemplateGroupSpec{
Title: "imported",
Content: amConfig.TemplateFiles["imported"],
Kind: v0alpha1.TemplateGroupTemplateKindMimir,
}, templates.Items[1].Spec)
assert.Equal(t,
v0alpha1.TemplateGroupSpec{
Title: "template",
Content: amConfig.TemplateFiles["template"],
Kind: v0alpha1.TemplateGroupTemplateKindMimir,
}, templates.Items[2].Spec)
})
t.Run("should be provisioned", func(t *testing.T) {
for _, tpl := range templates.Items[1:] {
assert.EqualValues(t, models.ProvenanceConvertedPrometheus, tpl.GetProvenanceStatus())
}
})
t.Run("should not be able to update", func(t *testing.T) {
tpl := templates.Items[1]
tpl.Spec.Content = "new content"
_, err := client.Update(context.Background(), &tpl, metav1.UpdateOptions{})
require.Truef(t, errors.IsBadRequest(err), "expected bad request but got %s", err)
})
t.Run("should not be able to delete", func(t *testing.T) {
err := client.Delete(context.Background(), templates.Items[1].Name, metav1.DeleteOptions{})
require.Truef(t, errors.IsBadRequest(err), "expected bad request but got %s", err)
})
t.Run("should not conflict with Grafana kind", func(t *testing.T) {
tpl := v0alpha1.TemplateGroup{
ObjectMeta: metav1.ObjectMeta{
Namespace: "default",
},
Spec: templates.Items[1].Spec,
}
tpl.Spec.Kind = v0alpha1.TemplateGroupTemplateKindGrafana
created, err := client.Create(context.Background(), &tpl, metav1.CreateOptions{})
require.NoError(t, err)
assert.NotEqual(t, templates.Items[1].Name, created.Name)
})
t.Run("sort by kind and then name", func(t *testing.T) {
templates, err := client.List(context.Background(), metav1.ListOptions{})
require.NoError(t, err)
require.Len(t, templates.Items, 4)
assert.Equal(t, v0alpha1.DefaultTemplateTitle, templates.Items[0].Spec.Title)
assert.Equal(t, "imported", templates.Items[1].Spec.Title)
assert.Equal(t, v0alpha1.TemplateGroupTemplateKindGrafana, templates.Items[1].Spec.Kind)
assert.Equal(t, "imported", templates.Items[2].Spec.Title)
assert.Equal(t, v0alpha1.TemplateGroupTemplateKindMimir, templates.Items[2].Spec.Kind)
assert.Equal(t, "template", templates.Items[3].Spec.Title)
})
}
@@ -54,6 +54,7 @@ func TestIntegrationResourceIdentifier(t *testing.T) {
Spec: v0alpha1.TemplateGroupSpec{
Title: "templateGroup",
Content: `{{ define "test" }} test {{ end }}`,
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
},
}
@@ -112,6 +113,7 @@ func TestIntegrationResourceIdentifier(t *testing.T) {
require.Equal(t, v0alpha1.TemplateGroupSpec{
Title: v0alpha1.DefaultTemplateTitle,
Content: defaultDefn.Template,
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
}, actual.Spec)
defaultTemplateGroup = actual
})
@@ -226,6 +228,7 @@ func TestIntegrationAccessControl(t *testing.T) {
Spec: v0alpha1.TemplateGroupSpec{
Title: fmt.Sprintf("template-group-1-%s", tc.user.Identity.GetLogin()),
Content: `{{ define "test" }} test {{ end }}`,
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
},
}
expected.SetProvenanceStatus("")
@@ -385,6 +388,7 @@ func TestIntegrationProvisioning(t *testing.T) {
Spec: v0alpha1.TemplateGroupSpec{
Title: "template-group-1",
Content: `{{ define "test" }} test {{ end }}`,
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
},
}, v1.CreateOptions{})
require.NoError(t, err)
@@ -428,6 +432,7 @@ func TestIntegrationOptimisticConcurrency(t *testing.T) {
Spec: v0alpha1.TemplateGroupSpec{
Title: "template-group-1",
Content: `{{ define "test" }} test {{ end }}`,
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
},
}
@@ -510,6 +515,7 @@ func TestIntegrationPatch(t *testing.T) {
Spec: v0alpha1.TemplateGroupSpec{
Title: "template-group",
Content: `{{ define "test" }} test {{ end }}`,
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
},
}
@@ -568,6 +574,7 @@ func TestIntegrationListSelector(t *testing.T) {
Spec: v0alpha1.TemplateGroupSpec{
Title: "test1",
Content: `{{ define "test1" }} test {{ end }}`,
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
},
}
template1, err := adminClient.Create(ctx, template1, v1.CreateOptions{})
@@ -580,6 +587,7 @@ func TestIntegrationListSelector(t *testing.T) {
Spec: v0alpha1.TemplateGroupSpec{
Title: "test2",
Content: `{{ define "test2" }} test {{ end }}`,
Kind: v0alpha1.TemplateGroupTemplateKindGrafana,
},
}
template2, err = adminClient.Create(ctx, template2, v1.CreateOptions{})
@@ -655,3 +663,37 @@ func TestIntegrationListSelector(t *testing.T) {
require.NotEqualf(t, templates.DefaultTemplateName, list.Items[1].Name, "Expected non-default template but got %s", list.Items[1].Name)
})
}
func TestIntegrationKinds(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
ctx := context.Background()
helper := getTestHelper(t)
client := common.NewTemplateGroupClient(t, helper.Org1.Admin)
newTemplate := &v0alpha1.TemplateGroup{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TemplateGroupSpec{
Title: "templateGroup",
Content: `{{ define "test" }} test {{ end }}`,
Kind: v0alpha1.TemplateGroupTemplateKindMimir,
},
}
t.Run("should not let create Mimir template", func(t *testing.T) {
_, err := client.Create(ctx, newTemplate, v1.CreateOptions{})
require.Truef(t, errors.IsBadRequest(err), "expected bad request but got %s", err)
})
t.Run("should not let change kind", func(t *testing.T) {
newTemplate.Spec.Kind = v0alpha1.TemplateGroupTemplateKindGrafana
created, err := client.Create(ctx, newTemplate, v1.CreateOptions{})
require.NoError(t, err)
created.Spec.Kind = v0alpha1.TemplateGroupTemplateKindMimir
_, err = client.Update(ctx, created, v1.UpdateOptions{})
require.Truef(t, errors.IsBadRequest(err), "expected bad request but got %s", err)
})
}
@@ -0,0 +1,15 @@
alertmanager_config: |
route:
receiver: sinkhole
group_by:
- alertname
receivers:
- name: sinkhole
template_files:
imported: |
{{ define "imported" }}
{{ end }}
template: |
{{ define "template" }}
{{ template "imported" . }}
{{ end }}
@@ -3783,18 +3783,29 @@
"type": "object",
"required": [
"title",
"content"
"content",
"kind"
],
"properties": {
"content": {
"type": "string"
},
"kind": {
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroupTemplateKind"
},
"title": {
"type": "string"
}
},
"additionalProperties": false
},
"com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TemplateGroupTemplateKind": {
"type": "string",
"enum": [
"grafana",
"mimir"
]
},
"com.github.grafana.grafana.apps.alerting.notifications.pkg.apis.alertingnotifications.v0alpha1.TimeInterval": {
"type": "object",
"required": [
@@ -5295,10 +5295,6 @@
"com.github.grafana.grafana.apps.provisioning.pkg.apis.provisioning.v0alpha1.MigrateJobOptions": {
"type": "object",
"properties": {
"history": {
"description": "Preserve history (if possible)",
"type": "boolean"
},
"message": {
"description": "Message to use when committing the changes in a single commit",
"type": "string"
@@ -5828,10 +5824,6 @@
"kind": {
"description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
"type": "string"
},
"legacyStorage": {
"description": "The backend is using legacy storage FIXME: Not sure where this should be exposed... but we need it somewhere The UI should force the onboarding workflow when this is true",
"type": "boolean"
}
}
},
+7
View File
@@ -10,7 +10,9 @@ import (
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/client-go/kubernetes"
"github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/tests/testsuite"
"github.com/grafana/grafana/pkg/util/testutil"
@@ -37,6 +39,11 @@ func TestIntegrationOpenAPIs(t *testing.T) {
featuremgmt.FlagKubernetesAlertingHistorian,
featuremgmt.FlagKubernetesLogsDrilldown,
},
// Explicitly configure with mode 5 the resources supported by provisioning.
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {DualWriterMode: rest.Mode5},
"folders.folder.grafana.app": {DualWriterMode: rest.Mode5},
},
})
t.Run("check valid version response", func(t *testing.T) {
+14 -14
View File
@@ -28,7 +28,7 @@ func TestIntegrationPluginMeta(t *testing.T) {
"apiVersion": "plugins.grafana.app/v0alpha1",
"kind": "Plugin",
"metadata": {"name": "%s"},
"spec": {"id": "grafana-piechart-panel", "version": "1.0.0"}
"spec": {"id": "piechart", "version": "1.0.0"}
}`, plugin1Name))
_, err := client.Resource.Create(ctx, plugin1, metav1.CreateOptions{})
require.NoError(t, err)
@@ -38,7 +38,7 @@ func TestIntegrationPluginMeta(t *testing.T) {
"apiVersion": "plugins.grafana.app/v0alpha1",
"kind": "Plugin",
"metadata": {"name": "%s"},
"spec": {"id": "grafana-clock-panel", "version": "1.0.0"}
"spec": {"id": "table", "version": "1.0.0"}
}`, plugin2Name))
_, err = client.Resource.Create(ctx, plugin2, metav1.CreateOptions{})
require.NoError(t, err)
@@ -57,14 +57,14 @@ func TestIntegrationPluginMeta(t *testing.T) {
foundIDs := make(map[string]bool)
for _, item := range response.Result.Items {
require.NotNil(t, item.Spec.PluginJSON)
foundIDs[item.Spec.PluginJSON.Id] = true
require.NotEmpty(t, item.Spec.PluginJSON.Id)
require.NotEmpty(t, item.Spec.PluginJSON.Type)
require.NotEmpty(t, item.Spec.PluginJSON.Name)
require.NotNil(t, item.Spec.PluginJson)
foundIDs[item.Spec.PluginJson.Id] = true
require.NotEmpty(t, item.Spec.PluginJson.Id)
require.NotEmpty(t, item.Spec.PluginJson.Type)
require.NotEmpty(t, item.Spec.PluginJson.Name)
}
require.True(t, foundIDs["grafana-piechart-panel"])
require.True(t, foundIDs["grafana-clock-panel"])
require.True(t, foundIDs["piechart"])
require.True(t, foundIDs["table"])
})
t.Run("list plugin metas with no plugins", func(t *testing.T) {
@@ -95,7 +95,7 @@ func TestIntegrationPluginMeta(t *testing.T) {
"apiVersion": "plugins.grafana.app/v0alpha1",
"kind": "Plugin",
"metadata": {"name": "%s"},
"spec": {"id": "grafana-piechart-panel", "version": "1.0.0"}
"spec": {"id": "piechart", "version": "1.0.0"}
}`, pluginName))
_, err := client.Resource.Create(ctx, plugin, metav1.CreateOptions{})
require.NoError(t, err)
@@ -109,10 +109,10 @@ func TestIntegrationPluginMeta(t *testing.T) {
}, &pluginsv0alpha1.Meta{})
require.NotNil(t, response.Result)
require.NotNil(t, response.Result.Spec.PluginJSON)
require.Equal(t, "grafana-piechart-panel", response.Result.Spec.PluginJSON.Id)
require.NotEmpty(t, response.Result.Spec.PluginJSON.Name)
require.NotEmpty(t, response.Result.Spec.PluginJSON.Type)
require.NotNil(t, response.Result.Spec.PluginJson)
require.Equal(t, "piechart", response.Result.Spec.PluginJson.Id)
require.NotEmpty(t, response.Result.Spec.PluginJson.Name)
require.NotEmpty(t, response.Result.Spec.PluginJson.Type)
})
t.Run("get plugin meta for non-existent plugin", func(t *testing.T) {
+7 -2
View File
@@ -681,12 +681,17 @@ func runGrafana(t *testing.T, options ...grafanaOption) *provisioningTestHelper
EnableFeatureToggles: []string{
featuremgmt.FlagProvisioning,
},
// Provisioning requires resources to be fully migrated to unified storage.
// Mode5 ensures reads/writes go to unified storage, and EnableMigration
// enables the data migration at startup to migrate legacy data.
UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{
"dashboards.dashboard.grafana.app": {
DualWriterMode: grafanarest.Mode5,
DualWriterMode: grafanarest.Mode5,
EnableMigration: true,
},
"folders.folder.grafana.app": {
DualWriterMode: grafanarest.Mode5,
DualWriterMode: grafanarest.Mode5,
EnableMigration: true,
},
},
PermittedProvisioningPaths: ".|" + provisioningPath,