| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | package resource | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"bytes" | 
					
						
							| 
									
										
										
										
											2024-10-17 18:18:29 +08:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2024-08-05 22:30:14 +08:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 	"io" | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 	"net/http" | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	"sort" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							|  |  |  | 	"strings" | 
					
						
							|  |  |  | 	"sync" | 
					
						
							|  |  |  | 	"sync/atomic" | 
					
						
							|  |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel/trace" | 
					
						
							|  |  |  | 	"go.opentelemetry.io/otel/trace/noop" | 
					
						
							|  |  |  | 	"gocloud.dev/blob" | 
					
						
							|  |  |  | 	_ "gocloud.dev/blob/fileblob" | 
					
						
							|  |  |  | 	_ "gocloud.dev/blob/memblob" | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | 
					
						
							| 
									
										
										
										
											2025-01-17 20:54:25 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/grafana/grafana/pkg/apimachinery/utils" | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type CDKBackendOptions struct { | 
					
						
							|  |  |  | 	Tracer     trace.Tracer | 
					
						
							| 
									
										
										
										
											2024-10-17 18:18:29 +08:00
										 |  |  | 	Bucket     CDKBucket | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	RootFolder string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func NewCDKBackend(ctx context.Context, opts CDKBackendOptions) (StorageBackend, error) { | 
					
						
							|  |  |  | 	if opts.Tracer == nil { | 
					
						
							|  |  |  | 		opts.Tracer = noop.NewTracerProvider().Tracer("cdk-appending-store") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if opts.Bucket == nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("missing bucket") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	found, _, err := opts.Bucket.ListPage(ctx, blob.FirstPageToken, 1, &blob.ListOptions{ | 
					
						
							|  |  |  | 		Prefix:    opts.RootFolder, | 
					
						
							|  |  |  | 		Delimiter: "/", | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if found == nil { | 
					
						
							|  |  |  | 		return nil, fmt.Errorf("the root folder does not exist") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	backend := &cdkBackend{ | 
					
						
							|  |  |  | 		tracer: opts.Tracer, | 
					
						
							|  |  |  | 		bucket: opts.Bucket, | 
					
						
							|  |  |  | 		root:   opts.RootFolder, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	backend.rv.Swap(time.Now().UnixMilli()) | 
					
						
							|  |  |  | 	return backend, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type cdkBackend struct { | 
					
						
							|  |  |  | 	tracer trace.Tracer | 
					
						
							| 
									
										
										
										
											2024-10-17 18:18:29 +08:00
										 |  |  | 	bucket CDKBucket | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	root   string | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex sync.Mutex | 
					
						
							|  |  |  | 	rv    atomic.Int64 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Simple watch stream -- NOTE, this only works for single tenant!
 | 
					
						
							|  |  |  | 	broadcaster Broadcaster[*WrittenEvent] | 
					
						
							|  |  |  | 	stream      chan<- *WrittenEvent | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *cdkBackend) getPath(key *ResourceKey, rv int64) string { | 
					
						
							|  |  |  | 	var buffer bytes.Buffer | 
					
						
							|  |  |  | 	buffer.WriteString(s.root) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if key.Group == "" { | 
					
						
							|  |  |  | 		return buffer.String() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	buffer.WriteString(key.Group) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if key.Resource == "" { | 
					
						
							|  |  |  | 		return buffer.String() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	buffer.WriteString("/") | 
					
						
							|  |  |  | 	buffer.WriteString(key.Resource) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if key.Namespace == "" { | 
					
						
							|  |  |  | 		if key.Name == "" { | 
					
						
							|  |  |  | 			return buffer.String() | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		buffer.WriteString("/__cluster__") | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		buffer.WriteString("/") | 
					
						
							|  |  |  | 		buffer.WriteString(key.Namespace) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if key.Name == "" { | 
					
						
							|  |  |  | 		return buffer.String() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	buffer.WriteString("/") | 
					
						
							|  |  |  | 	buffer.WriteString(key.Name) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if rv > 0 { | 
					
						
							|  |  |  | 		buffer.WriteString(fmt.Sprintf("/%d.json", rv)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return buffer.String() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-12-04 01:20:27 +08:00
										 |  |  | // GetResourceStats implements Backend.
 | 
					
						
							| 
									
										
										
										
											2024-12-05 18:58:13 +08:00
										 |  |  | func (s *cdkBackend) GetResourceStats(ctx context.Context, namespace string, minCount int) ([]ResourceStats, error) { | 
					
						
							| 
									
										
										
										
											2024-11-07 02:58:07 +08:00
										 |  |  | 	return nil, fmt.Errorf("not implemented") | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | func (s *cdkBackend) WriteEvent(ctx context.Context, event WriteEvent) (rv int64, err error) { | 
					
						
							|  |  |  | 	// Scope the lock
 | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		s.mutex.Lock() | 
					
						
							|  |  |  | 		defer s.mutex.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rv = s.rv.Add(1) | 
					
						
							|  |  |  | 		err = s.bucket.WriteAll(ctx, s.getPath(event.Key, rv), event.Value, &blob.WriterOptions{ | 
					
						
							|  |  |  | 			ContentType: "application/json", | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Async notify all subscribers
 | 
					
						
							|  |  |  | 	if s.stream != nil { | 
					
						
							|  |  |  | 		go func() { | 
					
						
							|  |  |  | 			write := &WrittenEvent{ | 
					
						
							| 
									
										
										
										
											2025-02-21 00:34:25 +08:00
										 |  |  | 				Type:            event.Type, | 
					
						
							|  |  |  | 				Key:             event.Key, | 
					
						
							|  |  |  | 				PreviousRV:      event.PreviousRV, | 
					
						
							|  |  |  | 				Value:           event.Value, | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 				Timestamp:       time.Now().UnixMilli(), | 
					
						
							|  |  |  | 				ResourceVersion: rv, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			s.stream <- write | 
					
						
							|  |  |  | 		}() | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return rv, err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-12 19:52:04 +08:00
										 |  |  | func (s *cdkBackend) ReadResource(ctx context.Context, req *ReadRequest) *BackendReadResponse { | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	rv := req.ResourceVersion | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 	path := s.getPath(req.Key, rv) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	if rv < 1 { | 
					
						
							|  |  |  | 		iter := s.bucket.List(&blob.ListOptions{Prefix: path + "/", Delimiter: "/"}) | 
					
						
							|  |  |  | 		for { | 
					
						
							|  |  |  | 			obj, err := iter.Next(ctx) | 
					
						
							| 
									
										
										
										
											2024-08-05 22:30:14 +08:00
										 |  |  | 			if errors.Is(err, io.EOF) { | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 				break | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if strings.HasSuffix(obj.Key, ".json") { | 
					
						
							|  |  |  | 				idx := strings.LastIndex(obj.Key, "/") + 1 | 
					
						
							|  |  |  | 				edx := strings.LastIndex(obj.Key, ".") | 
					
						
							|  |  |  | 				if idx > 0 { | 
					
						
							|  |  |  | 					v, err := strconv.ParseInt(obj.Key[idx:edx], 10, 64) | 
					
						
							|  |  |  | 					if err == nil && v > rv { | 
					
						
							|  |  |  | 						rv = v | 
					
						
							|  |  |  | 						path = obj.Key // find the path with biggest resource version
 | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	raw, err := s.bucket.ReadAll(ctx, path) | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 	if raw == nil && req.ResourceVersion > 0 { | 
					
						
							|  |  |  | 		if req.ResourceVersion > s.rv.Load() { | 
					
						
							| 
									
										
										
										
											2024-11-12 19:52:04 +08:00
										 |  |  | 			return &BackendReadResponse{ | 
					
						
							| 
									
										
										
										
											2024-07-30 18:16:16 +08:00
										 |  |  | 				Error: &ErrorResult{ | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 					Code:    http.StatusGatewayTimeout, | 
					
						
							| 
									
										
										
										
											2024-07-30 18:16:16 +08:00
										 |  |  | 					Reason:  string(metav1.StatusReasonTimeout), // match etcd behavior
 | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 					Message: "ResourceVersion is larger than max", | 
					
						
							| 
									
										
										
										
											2024-07-30 18:16:16 +08:00
										 |  |  | 					Details: &ErrorDetails{ | 
					
						
							|  |  |  | 						Causes: []*ErrorCause{ | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 							{ | 
					
						
							| 
									
										
										
										
											2024-07-30 18:16:16 +08:00
										 |  |  | 								Reason:  string(metav1.CauseTypeResourceVersionTooLarge), | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 								Message: fmt.Sprintf("requested: %d, current %d", req.ResourceVersion, s.rv.Load()), | 
					
						
							|  |  |  | 							}, | 
					
						
							|  |  |  | 						}, | 
					
						
							|  |  |  | 					}, | 
					
						
							|  |  |  | 				}, | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// If the there was an explicit request, get the latest
 | 
					
						
							| 
									
										
										
										
											2024-07-30 18:16:16 +08:00
										 |  |  | 		rsp := s.ReadResource(ctx, &ReadRequest{Key: req.Key}) | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 		if rsp != nil && len(rsp.Value) > 0 { | 
					
						
							|  |  |  | 			raw = rsp.Value | 
					
						
							|  |  |  | 			rv = rsp.ResourceVersion | 
					
						
							|  |  |  | 			err = nil | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2025-01-17 20:54:25 +08:00
										 |  |  | 	if err == nil && isDeletedValue(raw) { | 
					
						
							| 
									
										
										
										
											2024-07-16 22:43:15 +08:00
										 |  |  | 		raw = nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	if raw == nil { | 
					
						
							| 
									
										
										
										
											2024-11-12 19:52:04 +08:00
										 |  |  | 		return &BackendReadResponse{Error: NewNotFoundError(req.Key)} | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-11-12 19:52:04 +08:00
										 |  |  | 	return &BackendReadResponse{ | 
					
						
							|  |  |  | 		Key:             req.Key, | 
					
						
							|  |  |  | 		Folder:          "", // TODO: implement this
 | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		ResourceVersion: rv, | 
					
						
							|  |  |  | 		Value:           raw, | 
					
						
							| 
									
										
										
										
											2024-07-30 18:16:16 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-17 20:54:25 +08:00
										 |  |  | func isDeletedValue(raw []byte) bool { | 
					
						
							|  |  |  | 	if bytes.Contains(raw, []byte(`"generation":-999`)) { | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 		tmp := &unstructured.Unstructured{} | 
					
						
							|  |  |  | 		err := tmp.UnmarshalJSON(raw) | 
					
						
							| 
									
										
										
										
											2025-01-17 20:54:25 +08:00
										 |  |  | 		if err == nil && tmp.GetGeneration() == utils.DeletedGeneration { | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return false | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | func (s *cdkBackend) ListIterator(ctx context.Context, req *ListRequest, cb func(ListIterator) error) (int64, error) { | 
					
						
							| 
									
										
										
										
											2025-01-17 20:54:25 +08:00
										 |  |  | 	if req.Source != ListRequest_STORE { | 
					
						
							|  |  |  | 		return 0, fmt.Errorf("listing from history not supported in CDK backend") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	resources, err := buildTree(ctx, s, req.Options.Key) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 		return 0, err | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 	err = cb(resources) | 
					
						
							|  |  |  | 	return resources.listRV, err | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (s *cdkBackend) WatchWriteEvents(ctx context.Context) (<-chan *WrittenEvent, error) { | 
					
						
							|  |  |  | 	s.mutex.Lock() | 
					
						
							|  |  |  | 	defer s.mutex.Unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if s.broadcaster == nil { | 
					
						
							|  |  |  | 		var err error | 
					
						
							|  |  |  | 		s.broadcaster, err = NewBroadcaster(context.Background(), func(c chan<- *WrittenEvent) error { | 
					
						
							|  |  |  | 			s.stream = c | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, err | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return s.broadcaster.Subscribe(ctx) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // group > resource > namespace > name > versions
 | 
					
						
							|  |  |  | type cdkResource struct { | 
					
						
							|  |  |  | 	prefix   string | 
					
						
							|  |  |  | 	versions []cdkVersion | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | type cdkVersion struct { | 
					
						
							|  |  |  | 	rv  int64 | 
					
						
							|  |  |  | 	key string | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | type cdkListIterator struct { | 
					
						
							| 
									
										
										
										
											2024-10-17 18:18:29 +08:00
										 |  |  | 	bucket CDKBucket | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 	ctx    context.Context | 
					
						
							|  |  |  | 	err    error | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 	listRV    int64 | 
					
						
							|  |  |  | 	resources []cdkResource | 
					
						
							|  |  |  | 	index     int | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	currentRV  int64 | 
					
						
							|  |  |  | 	currentKey string | 
					
						
							|  |  |  | 	currentVal []byte | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Next implements ListIterator.
 | 
					
						
							|  |  |  | func (c *cdkListIterator) Next() bool { | 
					
						
							|  |  |  | 	if c.err != nil { | 
					
						
							|  |  |  | 		return false | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		c.currentVal = nil | 
					
						
							|  |  |  | 		c.index += 1 | 
					
						
							|  |  |  | 		if c.index >= len(c.resources) { | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		item := c.resources[c.index] | 
					
						
							|  |  |  | 		latest := item.versions[0] | 
					
						
							|  |  |  | 		raw, err := c.bucket.ReadAll(c.ctx, latest.key) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			c.err = err | 
					
						
							|  |  |  | 			return false | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2025-01-17 20:54:25 +08:00
										 |  |  | 		if !isDeletedValue(raw) { | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 			c.currentRV = latest.rv | 
					
						
							|  |  |  | 			c.currentKey = latest.key | 
					
						
							|  |  |  | 			c.currentVal = raw | 
					
						
							|  |  |  | 			return true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Error implements ListIterator.
 | 
					
						
							|  |  |  | func (c *cdkListIterator) Error() error { | 
					
						
							|  |  |  | 	return c.err | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ResourceVersion implements ListIterator.
 | 
					
						
							|  |  |  | func (c *cdkListIterator) ResourceVersion() int64 { | 
					
						
							|  |  |  | 	return c.currentRV | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Value implements ListIterator.
 | 
					
						
							|  |  |  | func (c *cdkListIterator) Value() []byte { | 
					
						
							|  |  |  | 	return c.currentVal | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // ContinueToken implements ListIterator.
 | 
					
						
							|  |  |  | func (c *cdkListIterator) ContinueToken() string { | 
					
						
							|  |  |  | 	return fmt.Sprintf("index:%d/key:%s", c.index, c.currentKey) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-01-28 22:17:52 +08:00
										 |  |  | // ContinueTokenWithCurrentRV implements ListIterator.
 | 
					
						
							|  |  |  | func (c *cdkListIterator) ContinueTokenWithCurrentRV() string { | 
					
						
							|  |  |  | 	return fmt.Sprintf("index:%d/key:%s", c.index, c.currentKey) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | // Name implements ListIterator.
 | 
					
						
							|  |  |  | func (c *cdkListIterator) Name() string { | 
					
						
							|  |  |  | 	return c.currentKey // TODO (parse name from key)
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // Namespace implements ListIterator.
 | 
					
						
							|  |  |  | func (c *cdkListIterator) Namespace() string { | 
					
						
							|  |  |  | 	return c.currentKey // TODO (parse namespace from key)
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-11-12 19:52:04 +08:00
										 |  |  | func (c *cdkListIterator) Folder() string { | 
					
						
							|  |  |  | 	return "" // TODO: implement this
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | var _ ListIterator = (*cdkListIterator)(nil) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func buildTree(ctx context.Context, s *cdkBackend, key *ResourceKey) (*cdkListIterator, error) { | 
					
						
							|  |  |  | 	byPrefix := make(map[string]*cdkResource) | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	path := s.getPath(key, 0) | 
					
						
							|  |  |  | 	iter := s.bucket.List(&blob.ListOptions{Prefix: path, Delimiter: ""}) // "" is recursive
 | 
					
						
							|  |  |  | 	for { | 
					
						
							|  |  |  | 		obj, err := iter.Next(ctx) | 
					
						
							| 
									
										
										
										
											2024-08-05 22:30:14 +08:00
										 |  |  | 		if errors.Is(err, io.EOF) { | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 			break | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		if strings.HasSuffix(obj.Key, ".json") { | 
					
						
							|  |  |  | 			idx := strings.LastIndex(obj.Key, "/") + 1 | 
					
						
							|  |  |  | 			edx := strings.LastIndex(obj.Key, ".") | 
					
						
							|  |  |  | 			if idx > 0 { | 
					
						
							|  |  |  | 				rv, err := strconv.ParseInt(obj.Key[idx:edx], 10, 64) | 
					
						
							|  |  |  | 				if err == nil { | 
					
						
							|  |  |  | 					prefix := obj.Key[:idx] | 
					
						
							|  |  |  | 					res, ok := byPrefix[prefix] | 
					
						
							|  |  |  | 					if !ok { | 
					
						
							|  |  |  | 						res = &cdkResource{prefix: prefix} | 
					
						
							|  |  |  | 						byPrefix[prefix] = res | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					res.versions = append(res.versions, cdkVersion{ | 
					
						
							|  |  |  | 						rv:  rv, | 
					
						
							|  |  |  | 						key: obj.Key, | 
					
						
							|  |  |  | 					}) | 
					
						
							|  |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Now sort all versions
 | 
					
						
							|  |  |  | 	resources := make([]cdkResource, 0, len(byPrefix)) | 
					
						
							|  |  |  | 	for _, res := range byPrefix { | 
					
						
							|  |  |  | 		sort.Slice(res.versions, func(i, j int) bool { | 
					
						
							|  |  |  | 			return res.versions[i].rv > res.versions[j].rv | 
					
						
							|  |  |  | 		}) | 
					
						
							|  |  |  | 		resources = append(resources, *res) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	sort.Slice(resources, func(i, j int) bool { | 
					
						
							| 
									
										
										
										
											2024-08-01 16:27:01 +08:00
										 |  |  | 		a := resources[i].prefix | 
					
						
							|  |  |  | 		b := resources[j].prefix | 
					
						
							|  |  |  | 		return a < b | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-07-31 17:05:59 +08:00
										 |  |  | 	return &cdkListIterator{ | 
					
						
							|  |  |  | 		ctx:       ctx, | 
					
						
							|  |  |  | 		bucket:    s.bucket, | 
					
						
							|  |  |  | 		resources: resources, | 
					
						
							|  |  |  | 		listRV:    s.rv.Load(), | 
					
						
							|  |  |  | 		index:     -1, // must call next first
 | 
					
						
							|  |  |  | 	}, nil | 
					
						
							| 
									
										
										
										
											2024-07-10 06:08:13 +08:00
										 |  |  | } |