| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | package resource | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"encoding/json" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2025-04-10 08:14:39 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	"os" | 
					
						
							|  |  |  | 	"testing" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/stretchr/testify/require" | 
					
						
							|  |  |  | 	"gocloud.dev/blob/fileblob" | 
					
						
							|  |  |  | 	"gocloud.dev/blob/memblob" | 
					
						
							|  |  |  | 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | 
					
						
							| 
									
										
										
										
											2024-11-13 00:58:32 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-21 17:06:55 +08:00
										 |  |  | 	claims "github.com/grafana/authlib/types" | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-13 00:58:32 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/apimachinery/identity" | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/apimachinery/utils" | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/storage/unified/resourcepb" | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func TestSimpleServer(t *testing.T) { | 
					
						
							|  |  |  | 	testUserA := &identity.StaticRequester{ | 
					
						
							| 
									
										
										
										
											2024-08-12 14:26:53 +08:00
										 |  |  | 		Type:           claims.TypeUser, | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		Login:          "testuser", | 
					
						
							|  |  |  | 		UserID:         123, | 
					
						
							|  |  |  | 		UserUID:        "u123", | 
					
						
							|  |  |  | 		OrgRole:        identity.RoleAdmin, | 
					
						
							|  |  |  | 		IsGrafanaAdmin: true, // can do anything
 | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-21 17:06:55 +08:00
										 |  |  | 	ctx := claims.WithAuthInfo(context.Background(), testUserA) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	bucket := memblob.OpenBucket(nil) | 
					
						
							|  |  |  | 	if false { | 
					
						
							|  |  |  | 		tmp, err := os.MkdirTemp("", "xxx-*") | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		bucket, err = fileblob.OpenBucket(tmp, &fileblob.Options{ | 
					
						
							|  |  |  | 			CreateDir: true, | 
					
						
							|  |  |  | 			Metadata:  fileblob.MetadataDontWrite, // skip
 | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		fmt.Printf("ROOT: %s\n\n", tmp) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	store, err := NewCDKBackend(ctx, CDKBackendOptions{ | 
					
						
							|  |  |  | 		Bucket: bucket, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	server, err := NewResourceServer(ResourceServerOptions{ | 
					
						
							|  |  |  | 		Backend: store, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("playlist happy CRUD paths", func(t *testing.T) { | 
					
						
							|  |  |  | 		raw := []byte(`{ | 
					
						
							| 
									
										
										
										
											2024-10-23 16:29:41 +08:00
										 |  |  |     		"apiVersion": "playlist.grafana.app/v0alpha1", | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 			"kind": "Playlist", | 
					
						
							|  |  |  | 			"metadata": { | 
					
						
							|  |  |  | 				"name": "fdgsv37qslr0ga", | 
					
						
							| 
									
										
										
										
											2024-11-13 00:58:32 +08:00
										 |  |  | 				"uid": "xyz", | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 				"namespace": "default", | 
					
						
							|  |  |  | 				"annotations": { | 
					
						
							| 
									
										
										
										
											2024-11-15 22:26:14 +08:00
										 |  |  | 					"grafana.app/repoName": "elsewhere", | 
					
						
							|  |  |  | 					"grafana.app/repoPath": "path/to/item", | 
					
						
							|  |  |  | 					"grafana.app/repoTimestamp": "2024-02-02T00:00:00Z" | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"spec": { | 
					
						
							|  |  |  | 				"title": "hello", | 
					
						
							|  |  |  | 				"interval": "5m", | 
					
						
							|  |  |  | 				"items": [ | 
					
						
							|  |  |  | 					{ | 
					
						
							|  |  |  | 						"type": "dashboard_by_uid", | 
					
						
							|  |  |  | 						"value": "vmie2cmWz" | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}`) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		key := &resourcepb.ResourceKey{ | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 			Group:     "playlist.grafana.app", | 
					
						
							|  |  |  | 			Resource:  "rrrr", // can be anything :(
 | 
					
						
							|  |  |  | 			Namespace: "default", | 
					
						
							|  |  |  | 			Name:      "fdgsv37qslr0ga", | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Should be empty when we start
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		all, err := server.List(ctx, &resourcepb.ListRequest{Options: &resourcepb.ListOptions{ | 
					
						
							|  |  |  | 			Key: &resourcepb.ResourceKey{ | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 				Group:    key.Group, | 
					
						
							|  |  |  | 				Resource: key.Resource, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}}) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.Len(t, all.Items, 0) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-10 08:14:39 +08:00
										 |  |  | 		// should return 404 if not found
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		found, err := server.Read(ctx, &resourcepb.ReadRequest{Key: key}) | 
					
						
							| 
									
										
										
										
											2025-04-10 08:14:39 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.NotNil(t, found.Error) | 
					
						
							|  |  |  | 		require.Equal(t, int32(http.StatusNotFound), found.Error.Code) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		created, err := server.Create(ctx, &resourcepb.CreateRequest{ | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 			Value: raw, | 
					
						
							|  |  |  | 			Key:   key, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 		require.Nil(t, created.Error) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		require.True(t, created.ResourceVersion > 0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// The key does not include resource version
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		found, err = server.Read(ctx, &resourcepb.ReadRequest{Key: key}) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 		require.Nil(t, found.Error) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		require.Equal(t, created.ResourceVersion, found.ResourceVersion) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Now update the value
 | 
					
						
							|  |  |  | 		tmp := &unstructured.Unstructured{} | 
					
						
							| 
									
										
										
										
											2024-07-17 22:40:03 +08:00
										 |  |  | 		err = json.Unmarshal(found.Value, tmp) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		now := time.Now().UnixMilli() | 
					
						
							|  |  |  | 		obj, err := utils.MetaAccessor(tmp) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		obj.SetAnnotation("test", "hello") | 
					
						
							|  |  |  | 		obj.SetUpdatedTimestampMillis(now) | 
					
						
							| 
									
										
										
										
											2024-07-30 13:27:23 +08:00
										 |  |  | 		obj.SetUpdatedBy(testUserA.GetUID()) | 
					
						
							| 
									
										
										
										
											2025-01-17 20:54:25 +08:00
										 |  |  | 		obj.SetLabels(map[string]string{ | 
					
						
							|  |  |  | 			utils.LabelKeyGetTrash: "", // should not be allowed to save this!
 | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		raw, err = json.Marshal(tmp) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		updated, err := server.Update(ctx, &resourcepb.UpdateRequest{ | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 			Key:             key, | 
					
						
							|  |  |  | 			Value:           raw, | 
					
						
							|  |  |  | 			ResourceVersion: created.ResourceVersion}) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2025-01-17 20:54:25 +08:00
										 |  |  | 		require.Equal(t, int32(400), updated.Error.Code) // bad request
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// remove the invalid labels
 | 
					
						
							|  |  |  | 		obj.SetLabels(nil) | 
					
						
							|  |  |  | 		raw, err = json.Marshal(tmp) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		updated, err = server.Update(ctx, &resourcepb.UpdateRequest{ | 
					
						
							| 
									
										
										
										
											2025-01-17 20:54:25 +08:00
										 |  |  | 			Key:             key, | 
					
						
							|  |  |  | 			Value:           raw, | 
					
						
							|  |  |  | 			ResourceVersion: created.ResourceVersion}) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 		require.Nil(t, updated.Error) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		require.True(t, updated.ResourceVersion > created.ResourceVersion) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// We should still get the latest
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		found, err = server.Read(ctx, &resourcepb.ReadRequest{Key: key}) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 		require.Nil(t, found.Error) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		require.Equal(t, updated.ResourceVersion, found.ResourceVersion) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		all, err = server.List(ctx, &resourcepb.ListRequest{Options: &resourcepb.ListOptions{ | 
					
						
							|  |  |  | 			Key: &resourcepb.ResourceKey{ | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 				Group:    key.Group, | 
					
						
							|  |  |  | 				Resource: key.Resource, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}}) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.Len(t, all.Items, 1) | 
					
						
							|  |  |  | 		require.Equal(t, updated.ResourceVersion, all.Items[0].ResourceVersion) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		deleted, err := server.Delete(ctx, &resourcepb.DeleteRequest{Key: key, ResourceVersion: updated.ResourceVersion}) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.True(t, deleted.ResourceVersion > updated.ResourceVersion) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// We should get not found status when trying to read the latest value
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		found, err = server.Read(ctx, &resourcepb.ReadRequest{Key: key}) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		require.NoError(t, err) | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 		require.NotNil(t, found.Error) | 
					
						
							|  |  |  | 		require.Equal(t, int32(404), found.Error.Code) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// And the deleted value should not be in the results
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		all, err = server.List(ctx, &resourcepb.ListRequest{Options: &resourcepb.ListOptions{ | 
					
						
							|  |  |  | 			Key: &resourcepb.ResourceKey{ | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 				Group:    key.Group, | 
					
						
							|  |  |  | 				Resource: key.Resource, | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 		}}) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 		require.Len(t, all.Items, 0) // empty
 | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2024-07-13 07:01:24 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	t.Run("playlist update optimistic concurrency check", func(t *testing.T) { | 
					
						
							|  |  |  | 		raw := []byte(`{ | 
					
						
							| 
									
										
										
										
											2024-10-23 16:29:41 +08:00
										 |  |  |     	"apiVersion": "playlist.grafana.app/v0alpha1", | 
					
						
							| 
									
										
										
										
											2024-07-13 07:01:24 +08:00
										 |  |  | 			"kind": "Playlist", | 
					
						
							|  |  |  | 			"metadata": { | 
					
						
							|  |  |  | 				"name": "fdgsv37qslr0ga", | 
					
						
							|  |  |  | 				"namespace": "default", | 
					
						
							| 
									
										
										
										
											2024-11-13 00:58:32 +08:00
										 |  |  | 				"uid": "xyz", | 
					
						
							| 
									
										
										
										
											2024-07-13 07:01:24 +08:00
										 |  |  | 				"annotations": { | 
					
						
							| 
									
										
										
										
											2024-11-15 22:26:14 +08:00
										 |  |  | 					"grafana.app/repoName": "elsewhere", | 
					
						
							|  |  |  | 					"grafana.app/repoPath": "path/to/item", | 
					
						
							|  |  |  | 					"grafana.app/repoTimestamp": "2024-02-02T00:00:00Z" | 
					
						
							| 
									
										
										
										
											2024-07-13 07:01:24 +08:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			}, | 
					
						
							|  |  |  | 			"spec": { | 
					
						
							|  |  |  | 				"title": "hello", | 
					
						
							|  |  |  | 				"interval": "5m", | 
					
						
							|  |  |  | 				"items": [ | 
					
						
							|  |  |  | 					{ | 
					
						
							|  |  |  | 						"type": "dashboard_by_uid", | 
					
						
							|  |  |  | 						"value": "vmie2cmWz" | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				] | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		}`) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		key := &resourcepb.ResourceKey{ | 
					
						
							| 
									
										
										
										
											2024-07-13 07:01:24 +08:00
										 |  |  | 			Group:     "playlist.grafana.app", | 
					
						
							|  |  |  | 			Resource:  "rrrr", // can be anything :(
 | 
					
						
							|  |  |  | 			Namespace: "default", | 
					
						
							|  |  |  | 			Name:      "fdgsv37qslr0ga", | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		created, err := server.Create(ctx, &resourcepb.CreateRequest{ | 
					
						
							| 
									
										
										
										
											2024-07-13 07:01:24 +08:00
										 |  |  | 			Value: raw, | 
					
						
							|  |  |  | 			Key:   key, | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// Update should return an ErrOptimisticLockingFailed the second time
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		_, err = server.Update(ctx, &resourcepb.UpdateRequest{ | 
					
						
							| 
									
										
										
										
											2024-07-13 07:01:24 +08:00
										 |  |  | 			Key:             key, | 
					
						
							|  |  |  | 			Value:           raw, | 
					
						
							|  |  |  | 			ResourceVersion: created.ResourceVersion}) | 
					
						
							|  |  |  | 		require.NoError(t, err) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-16 03:36:52 +08:00
										 |  |  | 		_, err = server.Update(ctx, &resourcepb.UpdateRequest{ | 
					
						
							| 
									
										
										
										
											2024-07-13 07:01:24 +08:00
										 |  |  | 			Key:             key, | 
					
						
							|  |  |  | 			Value:           raw, | 
					
						
							|  |  |  | 			ResourceVersion: created.ResourceVersion}) | 
					
						
							|  |  |  | 		require.ErrorIs(t, err, ErrOptimisticLockingFailed) | 
					
						
							|  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | } |