Alerting: Migrate notification models to generated by grafnaa-app-sdk (#95430)

* create notifications module and generate models

* switch template group to app models

* switch time intervals to use app models

* switch receiver to use app models

* switch routing tree to use app models

* move schema registration to resource packages

* fix package names to match app

* fix codeowners

* fix UI to use metadata.name instead of uid

* update dockerfile

* move generated models to pkg

* remove provenance from field selector

* move client factories to test files

* rename GenericClient to TypedClient
This commit is contained in:
Yuri Tseretyan
2024-12-09 10:29:05 -05:00
committed by GitHub
parent c4e19f3570
commit 2d386e6704
131 changed files with 4665 additions and 5933 deletions
@@ -20,13 +20,14 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/receiver/v0alpha1"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/generated/clientset/versioned"
notificationsv0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
@@ -72,22 +73,19 @@ func TestIntegrationResourceIdentifier(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
client := adminK8sClient.NotificationsV0alpha1().Receivers("default")
client := newClient(t, helper.Org1.Admin)
newResource := &v0alpha1.Receiver{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: "Test-Receiver",
Integrations: nil,
},
}
t.Run("create should fail if object name is specified", func(t *testing.T) {
resource := newResource.DeepCopy()
resource := newResource.Copy().(*v0alpha1.Receiver)
resource.Name = "new-receiver"
_, err := client.Create(ctx, resource, v1.CreateOptions{})
require.Truef(t, errors.IsBadRequest(err), "Expected BadRequest but got %s", err)
@@ -113,7 +111,7 @@ func TestIntegrationResourceIdentifier(t *testing.T) {
existing, err := client.Get(ctx, resourceID, v1.GetOptions{})
require.NoError(t, err)
updated := existing.DeepCopy()
updated := existing.Copy().(*v0alpha1.Receiver)
updated.Spec.Title = "another-newReceiver"
actual, err := client.Update(ctx, updated, v1.UpdateOptions{})
@@ -150,12 +148,6 @@ func TestIntegrationResourcePermissions(t *testing.T) {
),
})
newClient := func(t *testing.T, user apis.User) notificationsv0alpha1.ReceiverInterface {
k8sClient, err := versioned.NewForConfig(user.NewRestConfig())
require.NoError(t, err)
return k8sClient.NotificationsV0alpha1().Receivers("default")
}
admin := org1.Admin
viewer := org1.Viewer
editor := org1.Editor
@@ -318,7 +310,7 @@ func TestIntegrationResourcePermissions(t *testing.T) {
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: "receiver-1",
Integrations: nil,
},
@@ -349,7 +341,7 @@ func TestIntegrationResourcePermissions(t *testing.T) {
extractReceiverFromList := func(list *v0alpha1.ReceiverList, name string) *v0alpha1.Receiver {
for i := range list.Items {
if list.Items[i].Name == name {
return list.Items[i].DeepCopy()
return list.Items[i].Copy().(*v0alpha1.Receiver)
}
}
return nil
@@ -362,7 +354,7 @@ func TestIntegrationResourcePermissions(t *testing.T) {
require.NotNil(t, expectedGet)
// Set expected metadata.
expectedGetWithMetadata := expectedGet.DeepCopy()
expectedGetWithMetadata := expectedGet.Copy().(*v0alpha1.Receiver)
// Clear any existing access control metadata.
for _, k := range allACMetadata {
delete(expectedGetWithMetadata.Annotations, v0alpha1.AccessControlAnnotation(k))
@@ -375,7 +367,7 @@ func TestIntegrationResourcePermissions(t *testing.T) {
require.NoError(t, err)
expectedListWithMetadata := extractReceiverFromList(expectedList, created.Name)
require.NotNil(t, expectedListWithMetadata)
expectedListWithMetadata = expectedListWithMetadata.DeepCopy()
expectedListWithMetadata = expectedListWithMetadata.Copy().(*v0alpha1.Receiver)
// Clear any existing access control metadata.
for _, k := range allACMetadata {
delete(expectedListWithMetadata.Annotations, v0alpha1.AccessControlAnnotation(k))
@@ -575,22 +567,16 @@ func TestIntegrationAccessControl(t *testing.T) {
},
}
admin := org1.Admin
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
adminClient := newClient(t, helper.Org1.Admin)
for _, tc := range testCases {
t.Run(fmt.Sprintf("user '%s'", tc.user.Identity.GetLogin()), func(t *testing.T) {
k8sClient, err := versioned.NewForConfig(tc.user.NewRestConfig())
require.NoError(t, err)
client := k8sClient.NotificationsV0alpha1().Receivers("default")
client := newClient(t, tc.user)
var expected = &v0alpha1.Receiver{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: fmt.Sprintf("receiver-1-%s", tc.user.Identity.GetLogin()),
Integrations: nil,
},
@@ -598,7 +584,7 @@ func TestIntegrationAccessControl(t *testing.T) {
d, err := json.Marshal(expected)
require.NoError(t, err)
newReceiver := expected.DeepCopy()
newReceiver := expected.Copy().(*v0alpha1.Receiver)
newReceiver.Spec.Title = fmt.Sprintf("receiver-2-%s", tc.user.Identity.GetLogin())
if tc.canCreate {
t.Run("should be able to create receiver", func(t *testing.T) {
@@ -630,7 +616,7 @@ func TestIntegrationAccessControl(t *testing.T) {
if tc.canRead {
// Set expected metadata.
expectedWithMetadata := expected.DeepCopy()
expectedWithMetadata := expected.Copy().(*v0alpha1.Receiver)
expectedWithMetadata.SetInUse(0, nil)
if tc.canUpdate {
expectedWithMetadata.SetAccessControl("canWrite")
@@ -678,7 +664,7 @@ func TestIntegrationAccessControl(t *testing.T) {
})
}
updatedExpected := expected.DeepCopy()
updatedExpected := expected.Copy().(*v0alpha1.Receiver)
updatedExpected.Spec.Integrations = append(updatedExpected.Spec.Integrations, createIntegration(t, "email"))
d, err = json.Marshal(updatedExpected)
@@ -692,7 +678,7 @@ func TestIntegrationAccessControl(t *testing.T) {
expected = updated
t.Run("should get NotFound if name does not exist", func(t *testing.T) {
up := updatedExpected.DeepCopy()
up := updatedExpected.Copy().(*v0alpha1.Receiver)
up.Name = "notFound"
_, err := client.Update(ctx, up, v1.UpdateOptions{})
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
@@ -704,7 +690,7 @@ func TestIntegrationAccessControl(t *testing.T) {
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
t.Run("should get forbidden even if resource does not exist", func(t *testing.T) {
up := updatedExpected.DeepCopy()
up := updatedExpected.Copy().(*v0alpha1.Receiver)
up.Name = "notFound"
_, err := client.Update(ctx, up, v1.UpdateOptions{})
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
@@ -759,10 +745,7 @@ func TestIntegrationInUseMetadata(t *testing.T) {
cliCfg := helper.Org1.Admin.NewRestConfig()
legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
adminK8sClient, err := versioned.NewForConfig(cliCfg)
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
adminClient := newClient(t, helper.Org1.Admin)
// Prepare environment and create notification policy and rule that use receiver
alertmanagerRaw, err := testData.ReadFile(path.Join("test-data", "notification-settings.json"))
require.NoError(t, err)
@@ -884,10 +867,7 @@ func TestIntegrationProvisioning(t *testing.T) {
org := helper.Org1
admin := org.Admin
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
adminClient := newClient(t, helper.Org1.Admin)
env := helper.GetEnv()
ac := acimpl.ProvideAccessControl(env.FeatureToggles, zanzana.NewNoopClient())
db, err := store.ProvideDBStore(env.Cfg, env.FeatureToggles, env.SQLStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest()))
@@ -897,7 +877,7 @@ func TestIntegrationProvisioning(t *testing.T) {
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: "test-receiver-1",
Integrations: []v0alpha1.Integration{
createIntegration(t, "email"),
@@ -920,7 +900,7 @@ func TestIntegrationProvisioning(t *testing.T) {
t.Run("should not let update if provisioned", func(t *testing.T) {
got, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
require.NoError(t, err)
updated := got.DeepCopy()
updated := got.Copy().(*v0alpha1.Receiver)
updated.Spec.Integrations = append(updated.Spec.Integrations, createIntegration(t, "email"))
_, err = adminClient.Update(ctx, updated, v1.UpdateOptions{})
@@ -941,15 +921,12 @@ func TestIntegrationOptimisticConcurrency(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
adminClient := newClient(t, helper.Org1.Admin)
receiver := v0alpha1.Receiver{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: "receiver-1",
Integrations: nil,
},
@@ -961,13 +938,13 @@ func TestIntegrationOptimisticConcurrency(t *testing.T) {
require.NotEmpty(t, created.ResourceVersion)
t.Run("should forbid if version does not match", func(t *testing.T) {
updated := created.DeepCopy()
updated := created.Copy().(*v0alpha1.Receiver)
updated.ResourceVersion = "test"
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
require.Truef(t, errors.IsConflict(err), "should get Forbidden error but got %s", err)
})
t.Run("should update if version matches", func(t *testing.T) {
updated := created.DeepCopy()
updated := created.Copy().(*v0alpha1.Receiver)
updated.Spec.Integrations = append(updated.Spec.Integrations, createIntegration(t, "email"))
actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
require.NoError(t, err)
@@ -978,7 +955,7 @@ func TestIntegrationOptimisticConcurrency(t *testing.T) {
require.NotEqual(t, updated.ResourceVersion, actualUpdated.ResourceVersion)
})
t.Run("should fail to update if version is empty", func(t *testing.T) {
updated := created.DeepCopy()
updated := created.Copy().(*v0alpha1.Receiver)
updated.ResourceVersion = ""
updated.Spec.Integrations = append(updated.Spec.Integrations, createIntegration(t, "webhook"))
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
@@ -1027,15 +1004,12 @@ func TestIntegrationPatch(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
adminClient := newClient(t, helper.Org1.Admin)
receiver := v0alpha1.Receiver{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: "receiver",
Integrations: []v0alpha1.Integration{
createIntegration(t, "email"),
@@ -1083,9 +1057,9 @@ func TestIntegrationPatch(t *testing.T) {
}
expected := current.Spec.Integrations[index]
expected.Settings.Remove("username")
expected.Settings.Remove("password")
expected.Settings.Set("authorization_scheme", "bearer")
delete(expected.Settings, "username")
delete(expected.Settings, "password")
expected.Settings["authorization_scheme"] = "bearer"
delete(expected.SecureFields, "password")
expected.SecureFields["authorization_credentials"] = true
@@ -1128,9 +1102,7 @@ func TestIntegrationRejectConfigApiReceiverModification(t *testing.T) {
cliCfg := helper.Org1.Admin.NewRestConfig()
legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
adminK8sClient, err := versioned.NewForConfig(cliCfg)
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
adminClient := newClient(t, helper.Org1.Admin)
alertmanagerRaw, err := testData.ReadFile(path.Join("test-data", "notification-settings.json"))
require.NoError(t, err)
@@ -1205,10 +1177,7 @@ func TestIntegrationReferentialIntegrity(t *testing.T) {
cliCfg := helper.Org1.Admin.NewRestConfig()
legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
adminK8sClient, err := versioned.NewForConfig(cliCfg)
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
adminClient := newClient(t, helper.Org1.Admin)
// Prepare environment and create notification policy and rule that use time receiver
alertmanagerRaw, err := testData.ReadFile(path.Join("test-data", "notification-settings.json"))
require.NoError(t, err)
@@ -1240,7 +1209,7 @@ func TestIntegrationReferentialIntegrity(t *testing.T) {
t.Run("Update", func(t *testing.T) {
t.Run("should rename all references if name changes", func(t *testing.T) {
renamed := receiver.DeepCopy()
renamed := receiver.Copy().(*v0alpha1.Receiver)
expectedTitle := renamed.Spec.Title + "-new"
renamed.Spec.Title = expectedTitle
@@ -1265,7 +1234,7 @@ func TestIntegrationReferentialIntegrity(t *testing.T) {
t.Run("should fail if at least one resource is provisioned", func(t *testing.T) {
require.NoError(t, err)
renamed := receiver.DeepCopy()
renamed := receiver.Copy().(*v0alpha1.Receiver)
renamed.Spec.Title += util.GenerateShortUID()
t.Run("provisioned route", func(t *testing.T) {
@@ -1318,10 +1287,7 @@ func TestIntegrationCRUD(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
adminClient := newClient(t, helper.Org1.Admin)
var defaultReceiver *v0alpha1.Receiver
t.Run("should list the default receiver", func(t *testing.T) {
items, err := adminClient.List(ctx, v1.ListOptions{})
@@ -1343,19 +1309,19 @@ func TestIntegrationCRUD(t *testing.T) {
t.Run("should be able to update default receiver", func(t *testing.T) {
require.NotNil(t, defaultReceiver)
newDefault := defaultReceiver.DeepCopy()
newDefault := defaultReceiver.Copy().(*v0alpha1.Receiver)
newDefault.Spec.Integrations = append(newDefault.Spec.Integrations, createIntegration(t, "line"))
updatedReceiver, err := adminClient.Update(ctx, newDefault, v1.UpdateOptions{})
require.NoError(t, err)
expected := newDefault.DeepCopy()
expected := newDefault.Copy().(*v0alpha1.Receiver)
expected.Spec.Integrations[0].Uid = updatedReceiver.Spec.Integrations[0].Uid // default integration does not have UID before first update
lineIntegration := expected.Spec.Integrations[1]
lineIntegration.SecureFields = map[string]bool{
"token": true,
}
lineIntegration.Settings.Remove("token")
delete(lineIntegration.Settings, "token")
assert.Equal(t, "LINE", updatedReceiver.Spec.Integrations[1].Type) // this type is in the schema but not in backend
lineIntegration.Type = "LINE"
lineIntegration.Uid = updatedReceiver.Spec.Integrations[1].Uid
@@ -1369,7 +1335,7 @@ func TestIntegrationCRUD(t *testing.T) {
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: defaultReceiver.Spec.Title,
Integrations: nil,
},
@@ -1392,12 +1358,12 @@ func TestIntegrationCRUD(t *testing.T) {
for _, key := range keys {
integrations = append(integrations, createIntegration(t, key))
}
var err error
receiver, err = adminClient.Create(ctx, &v0alpha1.Receiver{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: "all-receivers",
Integrations: integrations,
},
@@ -1436,7 +1402,7 @@ func TestIntegrationCRUD(t *testing.T) {
assert.Contains(t, integration.SecureFields, field)
assert.Truef(t, integration.SecureFields[field], "secure field should be always true")
value, ok, err := unstructured.NestedString(integration.Settings.Object, strings.Split(field, ".")...)
value, ok, err := unstructured.NestedString(integration.Settings, strings.Split(field, ".")...)
assert.NoErrorf(t, err, "failed to read field %s from settings", field)
assert.Falsef(t, ok, "secret field %s should not be in settings, value [%s]", field, value)
}
@@ -1453,17 +1419,17 @@ func TestIntegrationCRUD(t *testing.T) {
t.Run(key, func(t *testing.T) {
integration := createIntegration(t, key)
// Make the integration invalid, so it fails to create. This is usually done by sending empty settings.
clear(integration.Settings.Object)
clear(integration.Settings)
if key == "webex" {
// Webex integration is special case and passes validation without any settings so we instead set an invalid URL.
integration.Settings.Set("api_url", "(*^$*^%!@#$*()")
integration.Settings["api_url"] = "(*^$*^%!@#$*()"
}
receiver, err = adminClient.Create(ctx, &v0alpha1.Receiver{
receiver, err := adminClient.Create(ctx, &v0alpha1.Receiver{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: fmt.Sprintf("invalid-%s", key),
Integrations: []v0alpha1.Integration{integration},
},
@@ -1483,30 +1449,26 @@ func TestIntegrationReceiverListSelector(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
require.NoError(t, err)
adminClient := newClient(t, helper.Org1.Admin)
recv1 := &v0alpha1.Receiver{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: "test-receiver-1",
Integrations: []v0alpha1.Integration{
createIntegration(t, "email"),
},
},
}
recv1, err = adminClient.Create(ctx, recv1, v1.CreateOptions{})
recv1, err := adminClient.Create(ctx, recv1, v1.CreateOptions{})
require.NoError(t, err)
recv2 := &v0alpha1.Receiver{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: "test-receiver-2",
Integrations: []v0alpha1.Integration{
createIntegration(t, "email"),
@@ -1551,7 +1513,7 @@ func TestIntegrationReceiverListSelector(t *testing.T) {
t.Run("should filter by multiple filters", func(t *testing.T) {
list, err := adminClient.List(ctx, v1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s,metadata.provenance=%s", recv2.Name, "API"),
FieldSelector: fmt.Sprintf("metadata.name=%s,spec.title=%s", recv2.Name, recv2.Spec.Title),
})
require.NoError(t, err)
require.Len(t, list.Items, 1)
@@ -1560,7 +1522,7 @@ func TestIntegrationReceiverListSelector(t *testing.T) {
t.Run("should be empty when filter does not match", func(t *testing.T) {
list, err := adminClient.List(ctx, v1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s,metadata.provenance=%s", recv2.Name, "unknown"),
FieldSelector: fmt.Sprintf("metadata.name=%s", "unknown"),
})
require.NoError(t, err)
require.Empty(t, list.Items)
@@ -1569,7 +1531,7 @@ func TestIntegrationReceiverListSelector(t *testing.T) {
// persistInitialConfig helps create an initial config with new receivers using legacy json. Config API blocks receiver
// modifications, so we need to use k8s API to create new receivers before posting the config.
func persistInitialConfig(t *testing.T, amConfig definitions.PostableUserConfig, adminClient notificationsv0alpha1.ReceiverInterface, legacyCli alerting.LegacyApiClient) {
func persistInitialConfig(t *testing.T, amConfig definitions.PostableUserConfig, adminClient *apis.TypedClient[v0alpha1.Receiver, v0alpha1.ReceiverList], legacyCli alerting.LegacyApiClient) {
ctx := context.Background()
var defaultReceiver *definitions.PostableApiReceiver
@@ -1583,7 +1545,7 @@ func persistInitialConfig(t *testing.T, amConfig definitions.PostableUserConfig,
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.ReceiverSpec{
Spec: v0alpha1.Spec{
Title: receiver.Name,
Integrations: []v0alpha1.Integration{},
},
@@ -1593,7 +1555,7 @@ func persistInitialConfig(t *testing.T, amConfig definitions.PostableUserConfig,
settings := common.Unstructured{}
require.NoError(t, settings.UnmarshalJSON(integration.Settings))
toCreate.Spec.Integrations = append(toCreate.Spec.Integrations, v0alpha1.Integration{
Settings: settings,
Settings: settings.Object,
Type: integration.Type,
DisableResolveMessage: util.Pointer(false),
})
@@ -1631,7 +1593,7 @@ func createIntegrationWithSettings(t *testing.T, integrationType string, setting
settings := common.Unstructured{}
require.NoError(t, settings.UnmarshalJSON([]byte(settingsJson)))
return v0alpha1.Integration{
Settings: settings,
Settings: settings.Object,
Type: integrationType,
DisableResolveMessage: util.Pointer(false),
}
@@ -1645,3 +1607,19 @@ func createWildcardPermission(actions ...string) resourcepermissions.SetResource
ResourceID: "*",
}
}
func newClient(t *testing.T, user apis.User) *apis.TypedClient[v0alpha1.Receiver, v0alpha1.ReceiverList] {
t.Helper()
client, err := dynamic.NewForConfig(user.NewRestConfig())
require.NoError(t, err)
return &apis.TypedClient[v0alpha1.Receiver, v0alpha1.ReceiverList]{
Client: client.Resource(
schema.GroupVersionResource{
Group: v0alpha1.Kind().Group(),
Version: v0alpha1.Kind().Version(),
Resource: v0alpha1.Kind().Plural(),
}).Namespace("default"),
}
}
@@ -1,4 +1,4 @@
package routing_tree
package routingtree
import (
"context"
@@ -16,10 +16,11 @@ import (
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/routingtree/v0alpha1"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/generated/clientset/versioned"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
@@ -57,21 +58,19 @@ func TestIntegrationNotAllowedMethods(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
client := adminK8sClient.NotificationsV0alpha1().RoutingTrees("default")
client := newClient(t, helper.Org1.Admin)
route := &v0alpha1.RoutingTree{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.RoutingTreeSpec{},
Spec: v0alpha1.Spec{},
}
_, err = client.Create(ctx, route, v1.CreateOptions{})
_, err := client.Create(ctx, route, v1.CreateOptions{})
assert.Error(t, err)
require.Truef(t, errors.IsMethodNotSupported(err), "Expected MethodNotSupported but got %s", err)
err = client.DeleteCollection(ctx, v1.DeleteOptions{}, v1.ListOptions{})
err = client.Client.DeleteCollection(ctx, v1.DeleteOptions{}, v1.ListOptions{})
assert.Error(t, err)
require.Truef(t, errors.IsMethodNotSupported(err), "Expected MethodNotSupported but got %s", err)
}
@@ -163,15 +162,11 @@ func TestIntegrationAccessControl(t *testing.T) {
}
admin := org1.Admin
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().RoutingTrees("default")
adminClient := newClient(t, admin)
for _, tc := range testCases {
t.Run(fmt.Sprintf("user '%s'", tc.user.Identity.GetLogin()), func(t *testing.T) {
k8sClient, err := versioned.NewForConfig(tc.user.NewRestConfig())
require.NoError(t, err)
client := k8sClient.NotificationsV0alpha1().RoutingTrees("default")
client := newClient(t, tc.user)
if tc.canRead {
t.Run("should be able to list routing trees", func(t *testing.T) {
@@ -212,7 +207,7 @@ func TestIntegrationAccessControl(t *testing.T) {
current, err := adminClient.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
require.NoError(t, err)
expected := current.DeepCopy()
expected := current.Copy().(*v0alpha1.RoutingTree)
expected.Spec.Routes = []v0alpha1.Route{
{
Matchers: []v0alpha1.Matcher{
@@ -236,7 +231,7 @@ func TestIntegrationAccessControl(t *testing.T) {
expected = updated
t.Run("should get NotFound if name does not exist", func(t *testing.T) {
up := expected.DeepCopy()
up := expected.Copy().(*v0alpha1.RoutingTree)
up.Name = "notFound"
_, err := client.Update(ctx, up, v1.UpdateOptions{})
require.Error(t, err)
@@ -250,7 +245,7 @@ func TestIntegrationAccessControl(t *testing.T) {
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
t.Run("should get forbidden even if resource does not exist", func(t *testing.T) {
up := expected.DeepCopy()
up := expected.Copy().(*v0alpha1.RoutingTree)
up.Name = "notFound"
_, err := client.Update(ctx, up, v1.UpdateOptions{})
require.Error(t, err)
@@ -286,7 +281,7 @@ func TestIntegrationAccessControl(t *testing.T) {
}
})
err = adminClient.Delete(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.DeleteOptions{})
err := adminClient.Delete(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.DeleteOptions{})
require.NoError(t, err)
}
}
@@ -302,9 +297,7 @@ func TestIntegrationProvisioning(t *testing.T) {
org := helper.Org1
admin := org.Admin
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().RoutingTrees("default")
adminClient := newClient(t, admin)
env := helper.GetEnv()
ac := acimpl.ProvideAccessControl(env.FeatureToggles, zanzana.NewNoopClient())
@@ -323,7 +316,7 @@ func TestIntegrationProvisioning(t *testing.T) {
require.Equal(t, "API", got.GetProvenanceStatus())
})
t.Run("should not let update if provisioned", func(t *testing.T) {
updated := current.DeepCopy()
updated := current.Copy().(*v0alpha1.RoutingTree)
updated.Spec.Routes = []v0alpha1.Route{
{
Matchers: []v0alpha1.Matcher{
@@ -355,23 +348,21 @@ func TestIntegrationOptimisticConcurrency(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().RoutingTrees("default")
adminClient := newClient(t, helper.Org1.Admin)
current, err := adminClient.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
require.NoError(t, err)
require.NotEmpty(t, current.ResourceVersion)
t.Run("should forbid if version does not match", func(t *testing.T) {
updated := current.DeepCopy()
updated := current.Copy().(*v0alpha1.RoutingTree)
updated.ResourceVersion = "test"
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
require.Error(t, err)
require.Truef(t, errors.IsConflict(err), "should get Forbidden error but got %s", err)
})
t.Run("should update if version matches", func(t *testing.T) {
updated := current.DeepCopy()
updated := current.Copy().(*v0alpha1.RoutingTree)
updated.Spec.Defaults.GroupBy = append(updated.Spec.Defaults.GroupBy, "data")
actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
require.NoError(t, err)
@@ -381,7 +372,7 @@ func TestIntegrationOptimisticConcurrency(t *testing.T) {
t.Run("should update if version is empty", func(t *testing.T) {
current, err = adminClient.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
require.NoError(t, err)
updated := current.DeepCopy()
updated := current.Copy().(*v0alpha1.RoutingTree)
updated.ResourceVersion = ""
updated.Spec.Routes = append(updated.Spec.Routes, v0alpha1.Route{Continue: true})
@@ -403,9 +394,7 @@ func TestIntegrationDataConsistency(t *testing.T) {
cliCfg := helper.Org1.Admin.NewRestConfig()
legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
adminK8sClient, err := versioned.NewForConfig(cliCfg)
require.NoError(t, err)
client := adminK8sClient.NotificationsV0alpha1().RoutingTrees("default")
client := newClient(t, helper.Org1.Admin)
receiver := "grafana-default-email"
timeInterval := "test-time-interval"
@@ -641,3 +630,19 @@ func TestIntegrationDataConsistency(t *testing.T) {
require.Equal(t, before, after)
})
}
func newClient(t *testing.T, user apis.User) *apis.TypedClient[v0alpha1.RoutingTree, v0alpha1.RoutingTreeList] {
t.Helper()
client, err := dynamic.NewForConfig(user.NewRestConfig())
require.NoError(t, err)
return &apis.TypedClient[v0alpha1.RoutingTree, v0alpha1.RoutingTreeList]{
Client: client.Resource(
schema.GroupVersionResource{
Group: v0alpha1.Kind().Group(),
Version: v0alpha1.Kind().Version(),
Resource: v0alpha1.Kind().Plural(),
}).Namespace("default"),
}
}
@@ -10,11 +10,12 @@ import (
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/templategroup/v0alpha1"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/generated/clientset/versioned"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
@@ -51,22 +52,20 @@ func TestIntegrationResourceIdentifier(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
client := adminK8sClient.NotificationsV0alpha1().TemplateGroups("default")
client := newClient(t, helper.Org1.Admin)
newTemplate := &v0alpha1.TemplateGroup{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TemplateGroupSpec{
Spec: v0alpha1.Spec{
Title: "templateGroup",
Content: `{{ define "test" }} test {{ end }}`,
},
}
t.Run("create should fail if object name is specified", func(t *testing.T) {
template := newTemplate.DeepCopy()
template := newTemplate.Copy().(*v0alpha1.TemplateGroup)
template.Name = "new-templateGroup"
_, err := client.Create(ctx, template, v1.CreateOptions{})
assert.Error(t, err)
@@ -95,7 +94,7 @@ func TestIntegrationResourceIdentifier(t *testing.T) {
if existingTemplateGroup == nil {
t.Skip()
}
updated := existingTemplateGroup.DeepCopy()
updated := existingTemplateGroup.Copy().(*v0alpha1.TemplateGroup)
updated.Spec.Title = "another-templateGroup"
actual, err := client.Update(ctx, updated, v1.UpdateOptions{})
require.NoError(t, err)
@@ -187,22 +186,17 @@ func TestIntegrationAccessControl(t *testing.T) {
},
}
admin := org1.Admin
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TemplateGroups("default")
adminClient := newClient(t, org1.Admin)
for _, tc := range testCases {
t.Run(fmt.Sprintf("user '%s'", tc.user.Identity.GetLogin()), func(t *testing.T) {
k8sClient, err := versioned.NewForConfig(tc.user.NewRestConfig())
require.NoError(t, err)
client := k8sClient.NotificationsV0alpha1().TemplateGroups("default")
client := newClient(t, tc.user)
var expected = &v0alpha1.TemplateGroup{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TemplateGroupSpec{
Spec: v0alpha1.Spec{
Title: fmt.Sprintf("template-group-1-%s", tc.user.Identity.GetLogin()),
Content: `{{ define "test" }} test {{ end }}`,
},
@@ -270,7 +264,7 @@ func TestIntegrationAccessControl(t *testing.T) {
})
}
updatedExpected := expected.DeepCopy()
updatedExpected := expected.Copy().(*v0alpha1.TemplateGroup)
updatedExpected.Spec.Content = `{{ define "another-test" }} test {{ end }}`
d, err = json.Marshal(updatedExpected)
@@ -284,7 +278,7 @@ func TestIntegrationAccessControl(t *testing.T) {
expected = updated
t.Run("should get NotFound if name does not exist", func(t *testing.T) {
up := updatedExpected.DeepCopy()
up := updatedExpected.Copy().(*v0alpha1.TemplateGroup)
up.Name = "notFound"
_, err := client.Update(ctx, up, v1.UpdateOptions{})
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
@@ -296,7 +290,7 @@ func TestIntegrationAccessControl(t *testing.T) {
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
t.Run("should get forbidden even if resource does not exist", func(t *testing.T) {
up := updatedExpected.DeepCopy()
up := updatedExpected.Copy().(*v0alpha1.TemplateGroup)
up.Name = "notFound"
_, err := client.Update(ctx, up, v1.UpdateOptions{})
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
@@ -351,9 +345,7 @@ func TestIntegrationProvisioning(t *testing.T) {
org := helper.Org1
admin := org.Admin
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TemplateGroups("default")
adminClient := newClient(t, admin)
env := helper.GetEnv()
ac := acimpl.ProvideAccessControl(env.FeatureToggles, zanzana.NewNoopClient())
@@ -364,7 +356,7 @@ func TestIntegrationProvisioning(t *testing.T) {
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TemplateGroupSpec{
Spec: v0alpha1.Spec{
Title: "template-group-1",
Content: `{{ define "test" }} test {{ end }}`,
},
@@ -382,7 +374,7 @@ func TestIntegrationProvisioning(t *testing.T) {
require.Equal(t, "API", got.GetProvenanceStatus())
})
t.Run("should not let update if provisioned", func(t *testing.T) {
updated := created.DeepCopy()
updated := created.Copy().(*v0alpha1.TemplateGroup)
updated.Spec.Content = `{{ define "another-test" }} test {{ end }}`
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
@@ -403,15 +395,13 @@ func TestIntegrationOptimisticConcurrency(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TemplateGroups("default")
adminClient := newClient(t, helper.Org1.Admin)
template := v0alpha1.TemplateGroup{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TemplateGroupSpec{
Spec: v0alpha1.Spec{
Title: "template-group-1",
Content: `{{ define "test" }} test {{ end }}`,
},
@@ -423,13 +413,13 @@ func TestIntegrationOptimisticConcurrency(t *testing.T) {
require.NotEmpty(t, created.ResourceVersion)
t.Run("should forbid if version does not match", func(t *testing.T) {
updated := created.DeepCopy()
updated := created.Copy().(*v0alpha1.TemplateGroup)
updated.ResourceVersion = "test"
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
require.Truef(t, errors.IsConflict(err), "should get Forbidden error but got %s", err)
})
t.Run("should update if version matches", func(t *testing.T) {
updated := created.DeepCopy()
updated := created.Copy().(*v0alpha1.TemplateGroup)
updated.Spec.Content = `{{ define "test-another" }} test {{ end }}`
actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
require.NoError(t, err)
@@ -437,7 +427,7 @@ func TestIntegrationOptimisticConcurrency(t *testing.T) {
require.NotEqual(t, updated.ResourceVersion, actualUpdated.ResourceVersion)
})
t.Run("should update if version is empty", func(t *testing.T) {
updated := created.DeepCopy()
updated := created.Copy().(*v0alpha1.TemplateGroup)
updated.ResourceVersion = ""
updated.Spec.Content = `{{ define "test-another-2" }} test {{ end }}`
@@ -489,15 +479,13 @@ func TestIntegrationPatch(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TemplateGroups("default")
adminClient := newClient(t, helper.Org1.Admin)
template := v0alpha1.TemplateGroup{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TemplateGroupSpec{
Spec: v0alpha1.Spec{
Title: "template-group",
Content: `{{ define "test" }} test {{ end }}`,
},
@@ -537,7 +525,7 @@ func TestIntegrationPatch(t *testing.T) {
result, err := adminClient.Patch(ctx, current.Name, types.JSONPatchType, patchData, v1.PatchOptions{})
require.NoError(t, err)
expectedSpec := *current.Spec.DeepCopy()
expectedSpec := current.Spec
expectedSpec.Content = expected
require.EqualValues(t, expectedSpec, result.Spec)
current = result
@@ -551,28 +539,25 @@ func TestIntegrationListSelector(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TemplateGroups("default")
adminClient := newClient(t, helper.Org1.Admin)
template1 := &v0alpha1.TemplateGroup{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TemplateGroupSpec{
Spec: v0alpha1.Spec{
Title: "test1",
Content: `{{ define "test1" }} test {{ end }}`,
},
}
template1, err = adminClient.Create(ctx, template1, v1.CreateOptions{})
template1, err := adminClient.Create(ctx, template1, v1.CreateOptions{})
require.NoError(t, err)
template2 := &v0alpha1.TemplateGroup{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TemplateGroupSpec{
Spec: v0alpha1.Spec{
Title: "test2",
Content: `{{ define "test2" }} test {{ end }}`,
},
@@ -614,7 +599,7 @@ func TestIntegrationListSelector(t *testing.T) {
t.Run("should filter by multiple filters", func(t *testing.T) {
list, err := adminClient.List(ctx, v1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s,metadata.provenance=%s", template2.Name, "API"),
FieldSelector: fmt.Sprintf("metadata.name=%s,spec.title=%s", template2.Name, template2.Spec.Title),
})
require.NoError(t, err)
require.Len(t, list.Items, 1)
@@ -623,9 +608,25 @@ func TestIntegrationListSelector(t *testing.T) {
t.Run("should be empty when filter does not match", func(t *testing.T) {
list, err := adminClient.List(ctx, v1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s,metadata.provenance=%s", template2.Name, "unknown"),
FieldSelector: fmt.Sprintf("metadata.name=%s", "unknown"),
})
require.NoError(t, err)
require.Empty(t, list.Items)
})
}
func newClient(t *testing.T, user apis.User) *apis.TypedClient[v0alpha1.TemplateGroup, v0alpha1.TemplateGroupList] {
t.Helper()
client, err := dynamic.NewForConfig(user.NewRestConfig())
require.NoError(t, err)
return &apis.TypedClient[v0alpha1.TemplateGroup, v0alpha1.TemplateGroupList]{
Client: client.Resource(
schema.GroupVersionResource{
Group: v0alpha1.Kind().Group(),
Version: v0alpha1.Kind().Version(),
Resource: v0alpha1.Kind().Plural(),
}).Namespace("default"),
}
}
@@ -15,11 +15,13 @@ import (
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/api/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/dynamic"
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/timeinterval/v0alpha1"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/resource/timeinterval/v0alpha1/fakes"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/generated/clientset/versioned"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
@@ -61,22 +63,20 @@ func TestIntegrationResourceIdentifier(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
client := adminK8sClient.NotificationsV0alpha1().TimeIntervals("default")
client := newClient(t, helper.Org1.Admin)
newInterval := &v0alpha1.TimeInterval{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TimeIntervalSpec{
Spec: v0alpha1.Spec{
Name: "time-newInterval",
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
},
}
t.Run("create should fail if object name is specified", func(t *testing.T) {
interval := newInterval.DeepCopy()
interval := newInterval.Copy().(*v0alpha1.TimeInterval)
interval.Name = "time-newInterval"
_, err := client.Create(ctx, interval, v1.CreateOptions{})
require.Truef(t, errors.IsBadRequest(err), "Expected BadRequest but got %s", err)
@@ -104,7 +104,7 @@ func TestIntegrationResourceIdentifier(t *testing.T) {
if existingInterval == nil {
t.Skip()
}
updated := existingInterval.DeepCopy()
updated := existingInterval.Copy().(*v0alpha1.TimeInterval)
updated.Spec.Name = "another-newInterval"
actual, err := client.Update(ctx, updated, v1.UpdateOptions{})
require.NoError(t, err)
@@ -197,24 +197,18 @@ func TestIntegrationTimeIntervalAccessControl(t *testing.T) {
},
}
admin := org1.Admin
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TimeIntervals("default")
adminClient := newClient(t, helper.Org1.Admin)
for _, tc := range testCases {
t.Run(fmt.Sprintf("user '%s'", tc.user.Identity.GetLogin()), func(t *testing.T) {
k8sClient, err := versioned.NewForConfig(tc.user.NewRestConfig())
require.NoError(t, err)
client := k8sClient.NotificationsV0alpha1().TimeIntervals("default")
client := newClient(t, tc.user)
var expected = &v0alpha1.TimeInterval{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TimeIntervalSpec{
Spec: v0alpha1.Spec{
Name: fmt.Sprintf("time-interval-1-%s", tc.user.Identity.GetLogin()),
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
},
}
expected.SetProvenanceStatus("")
@@ -280,8 +274,8 @@ func TestIntegrationTimeIntervalAccessControl(t *testing.T) {
})
}
updatedExpected := expected.DeepCopy()
updatedExpected.Spec.TimeIntervals = v0alpha1.IntervalGenerator{}.GenerateMany(2)
updatedExpected := expected.Copy().(*v0alpha1.TimeInterval)
updatedExpected.Spec.TimeIntervals = fakes.IntervalGenerator{}.GenerateMany(2)
d, err = json.Marshal(updatedExpected)
require.NoError(t, err)
@@ -294,7 +288,7 @@ func TestIntegrationTimeIntervalAccessControl(t *testing.T) {
expected = updated
t.Run("should get NotFound if name does not exist", func(t *testing.T) {
up := updatedExpected.DeepCopy()
up := updatedExpected.Copy().(*v0alpha1.TimeInterval)
up.Name = "notFound"
_, err := client.Update(ctx, up, v1.UpdateOptions{})
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
@@ -306,7 +300,7 @@ func TestIntegrationTimeIntervalAccessControl(t *testing.T) {
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
t.Run("should get forbidden even if resource does not exist", func(t *testing.T) {
up := updatedExpected.DeepCopy()
up := updatedExpected.Copy().(*v0alpha1.TimeInterval)
up.Name = "notFound"
_, err := client.Update(ctx, up, v1.UpdateOptions{})
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
@@ -361,9 +355,7 @@ func TestIntegrationTimeIntervalProvisioning(t *testing.T) {
org := helper.Org1
admin := org.Admin
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TimeIntervals("default")
adminClient := newClient(t, helper.Org1.Admin)
env := helper.GetEnv()
ac := acimpl.ProvideAccessControl(env.FeatureToggles, zanzana.NewNoopClient())
@@ -374,9 +366,9 @@ func TestIntegrationTimeIntervalProvisioning(t *testing.T) {
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TimeIntervalSpec{
Spec: v0alpha1.Spec{
Name: "time-interval-1",
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
},
}, v1.CreateOptions{})
require.NoError(t, err)
@@ -394,8 +386,8 @@ func TestIntegrationTimeIntervalProvisioning(t *testing.T) {
require.Equal(t, "API", got.GetProvenanceStatus())
})
t.Run("should not let update if provisioned", func(t *testing.T) {
updated := created.DeepCopy()
updated.Spec.TimeIntervals = v0alpha1.IntervalGenerator{}.GenerateMany(2)
updated := created.Copy().(*v0alpha1.TimeInterval)
updated.Spec.TimeIntervals = fakes.IntervalGenerator{}.GenerateMany(2)
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
@@ -415,17 +407,15 @@ func TestIntegrationTimeIntervalOptimisticConcurrency(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TimeIntervals("default")
adminClient := newClient(t, helper.Org1.Admin)
interval := v0alpha1.TimeInterval{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TimeIntervalSpec{
Spec: v0alpha1.Spec{
Name: "time-interval",
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
},
}
@@ -435,23 +425,23 @@ func TestIntegrationTimeIntervalOptimisticConcurrency(t *testing.T) {
require.NotEmpty(t, created.ResourceVersion)
t.Run("should forbid if version does not match", func(t *testing.T) {
updated := created.DeepCopy()
updated := created.Copy().(*v0alpha1.TimeInterval)
updated.ResourceVersion = "test"
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
require.Truef(t, errors.IsConflict(err), "should get Forbidden error but got %s", err)
})
t.Run("should update if version matches", func(t *testing.T) {
updated := created.DeepCopy()
updated.Spec.TimeIntervals = v0alpha1.IntervalGenerator{}.GenerateMany(2)
updated := created.Copy().(*v0alpha1.TimeInterval)
updated.Spec.TimeIntervals = fakes.IntervalGenerator{}.GenerateMany(2)
actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
require.NoError(t, err)
require.EqualValues(t, updated.Spec, actualUpdated.Spec)
require.NotEqual(t, updated.ResourceVersion, actualUpdated.ResourceVersion)
})
t.Run("should update if version is empty", func(t *testing.T) {
updated := created.DeepCopy()
updated := created.Copy().(*v0alpha1.TimeInterval)
updated.ResourceVersion = ""
updated.Spec.TimeIntervals = v0alpha1.IntervalGenerator{}.GenerateMany(2)
updated.Spec.TimeIntervals = fakes.IntervalGenerator{}.GenerateMany(2)
actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
require.NoError(t, err)
@@ -501,17 +491,15 @@ func TestIntegrationTimeIntervalPatch(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TimeIntervals("default")
adminClient := newClient(t, helper.Org1.Admin)
interval := v0alpha1.TimeInterval{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TimeIntervalSpec{
Spec: v0alpha1.Spec{
Name: "time-interval",
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
},
}
@@ -534,7 +522,7 @@ func TestIntegrationTimeIntervalPatch(t *testing.T) {
})
t.Run("should patch with json patch", func(t *testing.T) {
expected := v0alpha1.IntervalGenerator{}.Generate()
expected := fakes.IntervalGenerator{}.Generate()
patch := []map[string]interface{}{
{
@@ -549,9 +537,11 @@ func TestIntegrationTimeIntervalPatch(t *testing.T) {
result, err := adminClient.Patch(ctx, current.Name, types.JSONPatchType, patchData, v1.PatchOptions{})
require.NoError(t, err)
expectedSpec := *current.Spec.DeepCopy()
expectedSpec.TimeIntervals = []v0alpha1.Interval{
expected,
expectedSpec := v0alpha1.Spec{
Name: current.Spec.Name,
TimeIntervals: []v0alpha1.Interval{
expected,
},
}
require.EqualValues(t, expectedSpec, result.Spec)
current = result
@@ -566,29 +556,27 @@ func TestIntegrationTimeIntervalListSelector(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TimeIntervals("default")
adminClient := newClient(t, helper.Org1.Admin)
interval1 := &v0alpha1.TimeInterval{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TimeIntervalSpec{
Spec: v0alpha1.Spec{
Name: "test1",
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
},
}
interval1, err = adminClient.Create(ctx, interval1, v1.CreateOptions{})
interval1, err := adminClient.Create(ctx, interval1, v1.CreateOptions{})
require.NoError(t, err)
interval2 := &v0alpha1.TimeInterval{
ObjectMeta: v1.ObjectMeta{
Namespace: "default",
},
Spec: v0alpha1.TimeIntervalSpec{
Spec: v0alpha1.Spec{
Name: "test2",
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(2),
},
}
interval2, err = adminClient.Create(ctx, interval2, v1.CreateOptions{})
@@ -630,7 +618,7 @@ func TestIntegrationTimeIntervalListSelector(t *testing.T) {
t.Run("should filter by multiple filters", func(t *testing.T) {
list, err := adminClient.List(ctx, v1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s,metadata.provenance=%s", interval2.Name, "API"),
FieldSelector: fmt.Sprintf("metadata.name=%s,spec.name=%s", interval2.Name, interval2.Spec.Name),
})
require.NoError(t, err)
require.Len(t, list.Items, 1)
@@ -639,7 +627,7 @@ func TestIntegrationTimeIntervalListSelector(t *testing.T) {
t.Run("should be empty when filter does not match", func(t *testing.T) {
list, err := adminClient.List(ctx, v1.ListOptions{
FieldSelector: fmt.Sprintf("metadata.name=%s,metadata.provenance=%s", interval2.Name, "unknown"),
FieldSelector: fmt.Sprintf("metadata.name=%s", "unknown"),
})
require.NoError(t, err)
require.Empty(t, list.Items)
@@ -684,9 +672,7 @@ func TestIntegrationTimeIntervalReferentialIntegrity(t *testing.T) {
currentRoute := legacyCli.GetRoute(t)
currentRuleGroup := legacyCli.GetRulesGroup(t, folderUID, ruleGroup.Name)
adminK8sClient, err := versioned.NewForConfig(cliCfg)
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TimeIntervals("default")
adminClient := newClient(t, helper.Org1.Admin)
intervals, err := adminClient.List(ctx, v1.ListOptions{})
require.NoError(t, err)
@@ -710,7 +696,7 @@ func TestIntegrationTimeIntervalReferentialIntegrity(t *testing.T) {
t.Run("Update", func(t *testing.T) {
t.Run("should rename all references if name changes", func(t *testing.T) {
renamed := interval.DeepCopy()
renamed := interval.Copy().(*v0alpha1.TimeInterval)
renamed.Spec.Name += "-new"
actual, err := adminClient.Update(ctx, renamed, v1.UpdateOptions{})
@@ -734,7 +720,7 @@ func TestIntegrationTimeIntervalReferentialIntegrity(t *testing.T) {
t.Run("should fail if at least one resource is provisioned", func(t *testing.T) {
require.NoError(t, err)
renamed := interval.DeepCopy()
renamed := interval.Copy().(*v0alpha1.TimeInterval)
renamed.Spec.Name += util.GenerateShortUID()
t.Run("provisioned route", func(t *testing.T) {
@@ -787,24 +773,22 @@ func TestIntegrationTimeIntervalValidation(t *testing.T) {
ctx := context.Background()
helper := getTestHelper(t)
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TimeIntervals("default")
adminClient := newClient(t, helper.Org1.Admin)
testCases := []struct {
name string
interval v0alpha1.TimeIntervalSpec
interval v0alpha1.Spec
}{
{
name: "missing name",
interval: v0alpha1.TimeIntervalSpec{
interval: v0alpha1.Spec{
Name: "",
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(1),
TimeIntervals: fakes.IntervalGenerator{}.GenerateMany(1),
},
},
{
name: "invalid interval",
interval: v0alpha1.TimeIntervalSpec{
interval: v0alpha1.Spec{
Name: "test",
TimeIntervals: []v0alpha1.Interval{
{
@@ -823,9 +807,25 @@ func TestIntegrationTimeIntervalValidation(t *testing.T) {
},
Spec: tc.interval,
}
_, err = adminClient.Create(ctx, i, v1.CreateOptions{})
_, err := adminClient.Create(ctx, i, v1.CreateOptions{})
require.Error(t, err)
require.Truef(t, errors.IsBadRequest(err), "Expected BadRequest, got: %s", err)
})
}
}
func newClient(t *testing.T, user apis.User) *apis.TypedClient[v0alpha1.TimeInterval, v0alpha1.TimeIntervalList] {
t.Helper()
client, err := dynamic.NewForConfig(user.NewRestConfig())
require.NoError(t, err)
return &apis.TypedClient[v0alpha1.TimeInterval, v0alpha1.TimeIntervalList]{
Client: client.Resource(
schema.GroupVersionResource{
Group: v0alpha1.Kind().Group(),
Version: v0alpha1.Kind().Version(),
Resource: v0alpha1.Kind().Plural(),
}).Namespace("default"),
}
}
+85
View File
@@ -19,6 +19,7 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
"k8s.io/apimachinery/pkg/types"
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/discovery"
"k8s.io/client-go/dynamic"
@@ -645,3 +646,87 @@ func (c *K8sTestHelper) CreateTeam(name, email string, orgID int64) team.Team {
require.NoError(c.t, err)
return team
}
// TypedClient is the struct that implements a typed interface for resource operations
type TypedClient[T any, L any] struct {
Client dynamic.ResourceInterface
}
func (c *TypedClient[T, L]) Create(ctx context.Context, resource *T, opts metav1.CreateOptions) (*T, error) {
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(resource)
if err != nil {
return nil, err
}
u := &unstructured.Unstructured{Object: unstructuredObj}
result, err := c.Client.Create(ctx, u, opts)
if err != nil {
return nil, err
}
createdObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, createdObj)
if err != nil {
return nil, err
}
return createdObj, nil
}
func (c *TypedClient[T, L]) Update(ctx context.Context, resource *T, opts metav1.UpdateOptions) (*T, error) {
unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(resource)
if err != nil {
return nil, err
}
u := &unstructured.Unstructured{Object: unstructuredObj}
result, err := c.Client.Update(ctx, u, opts)
if err != nil {
return nil, err
}
updatedObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, updatedObj)
if err != nil {
return nil, err
}
return updatedObj, nil
}
func (c *TypedClient[T, L]) Delete(ctx context.Context, name string, opts metav1.DeleteOptions) error {
return c.Client.Delete(ctx, name, opts)
}
func (c *TypedClient[T, L]) Get(ctx context.Context, name string, opts metav1.GetOptions) (*T, error) {
result, err := c.Client.Get(ctx, name, opts)
if err != nil {
return nil, err
}
retrievedObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, retrievedObj)
if err != nil {
return nil, err
}
return retrievedObj, nil
}
func (c *TypedClient[T, L]) List(ctx context.Context, opts metav1.ListOptions) (*L, error) {
result, err := c.Client.List(ctx, opts)
if err != nil {
return nil, err
}
listObj := new(L)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.UnstructuredContent(), listObj)
if err != nil {
return nil, err
}
return listObj, nil
}
func (c *TypedClient[T, L]) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts metav1.PatchOptions, subresources ...string) (*T, error) {
result, err := c.Client.Patch(ctx, name, pt, data, opts, subresources...)
if err != nil {
return nil, err
}
patchedObj := new(T)
err = runtime.DefaultUnstructuredConverter.FromUnstructured(result.Object, patchedObj)
if err != nil {
return nil, err
}
return patchedObj, nil
}