diff --git a/go.work.sum b/go.work.sum index 20be75d93ca..6a5b806649c 100644 --- a/go.work.sum +++ b/go.work.sum @@ -575,6 +575,9 @@ github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWet github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0= +github.com/hack-pad/hackpadfs v0.2.1/go.mod h1:khQBuCEwGXWakkmq8ZiFUvUZz84ZkJ2KNwKvChs4OrU= +github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= github.com/hamba/avro/v2 v2.17.2 h1:6PKpEWzJfNnvBgn7m2/8WYaDOUASxfDU+Jyb4ojDgFY= github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK8fJ7Jo= diff --git a/pkg/storage/unified/resource/event.go b/pkg/storage/unified/resource/event.go index 4e48d626600..83d5aa12d7a 100644 --- a/pkg/storage/unified/resource/event.go +++ b/pkg/storage/unified/resource/event.go @@ -13,7 +13,8 @@ type WriteEvent struct { Key *ResourceKey // the request key Requester identity.Requester Operation ResourceOperation - PreviousRV int64 // only for Update+Delete + PreviousRV int64 // only for Update+Delete + Message string // commit message // The raw JSON payload // NOTE, this is never mutated, only parsed and validated diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index a0e2af17abd..92a726e54e8 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -13,6 +13,7 @@ require ( google.golang.org/grpc v1.64.0 google.golang.org/protobuf v1.34.1 k8s.io/apimachinery v0.29.3 + github.com/hack-pad/hackpadfs v0.2.1 ) require ( diff --git a/pkg/storage/unified/resource/go.sum b/pkg/storage/unified/resource/go.sum index 723014c2a31..3a15b5ffe19 100644 --- a/pkg/storage/unified/resource/go.sum +++ b/pkg/storage/unified/resource/go.sum @@ -12,6 +12,7 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240613114114-5e2f08de316d h1:/UE5JdF+0hxll7EuuO7zRzAxXrvAxQo5M9eqOepc2mQ= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/hack-pad/hackpadfs v0.2.1 h1:FelFhIhv26gyjujoA/yeFO+6YGlqzmc9la/6iKMIxMw= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= diff --git a/pkg/storage/unified/resource/mem.go b/pkg/storage/unified/resource/mem.go deleted file mode 100644 index 6d96dafdb13..00000000000 --- a/pkg/storage/unified/resource/mem.go +++ /dev/null @@ -1,131 +0,0 @@ -package resource - -import ( - context "context" - "crypto/sha1" - "encoding/base64" - "sync" - "sync/atomic" -) - -type MemoryStore interface { - Read(context.Context, *ReadRequest) (*ReadResponse, error) - WriteEvent(context.Context, *WriteEvent) (int64, error) -} - -func NewMemoryStore() MemoryStore { - return &memoryStore{ - store: make(map[string]*namespacedResources), - } -} - -type memoryStore struct { - counter atomic.Int64 - mutex sync.RWMutex - - // Key is group+resource - store map[string]*namespacedResources -} - -type namespacedResources struct { - // Lookup by resource name - namespace map[string]*resourceInfo -} - -type resourceInfo struct { - history []resourceValue -} - -type resourceValue struct { - rv int64 - event WriteEvent // saves the whole thing for now - blobHash string -} - -func (s *memoryStore) get(key *ResourceKey) *resourceValue { - s.mutex.RLock() - defer s.mutex.RUnlock() - - found, ok := s.store[key.Group+"/"+key.Resource] - if !ok || found.namespace == nil { - return nil - } - resource, ok := found.namespace[key.Namespace] - if !ok || len(resource.history) < 1 { - return nil - } - if key.ResourceVersion > 0 { - for idx, v := range resource.history { - if v.rv == key.ResourceVersion { - return &resource.history[idx] - } - } - } - latest := resource.history[0] - if latest.event.Operation == ResourceOperation_DELETED { - return nil - } - return &latest // the first one -} - -func (s *memoryStore) Read(_ context.Context, req *ReadRequest) (*ReadResponse, error) { - val := s.get(req.Key) - if val == nil { - return &ReadResponse{ - Status: &StatusResult{ - Status: "Failure", - Reason: "not found", - Code: 404, - }, - }, nil - } - rsp := &ReadResponse{ - ResourceVersion: val.rv, - Value: val.event.Value, - } - if val.event.Blob != nil { - rsp.BlobUrl = "#blob" - } - return rsp, nil -} - -func (s *memoryStore) WriteEvent(_ context.Context, req *WriteEvent) (int64, error) { - s.mutex.Lock() - defer s.mutex.Unlock() - - val := resourceValue{ - rv: s.counter.Add(1), - event: *req, - } - if req.Blob != nil { - hasher := sha1.New() - _, err := hasher.Write(req.Blob.Value) - if err != nil { - return 0, err - } - val.blobHash = base64.URLEncoding.EncodeToString(hasher.Sum(nil)) - } - - // Now append the value - key := req.Key - found, ok := s.store[key.Group+"/"+key.Resource] - if !ok { - found = &namespacedResources{} - s.store[key.Group+"/"+key.Resource] = found - } - if found.namespace == nil { - found.namespace = make(map[string]*resourceInfo) - } - - resource, ok := found.namespace[key.Namespace] - if !ok { - resource = &resourceInfo{} - found.namespace[key.Namespace] = resource - } - if resource.history == nil { - resource.history = []resourceValue{val} - } else { - resource.history = append([]resourceValue{val}, resource.history...) - } - return val.rv, nil -} diff --git a/pkg/storage/unified/resource/store.go b/pkg/storage/unified/resource/store.go new file mode 100644 index 00000000000..72df44ae4e2 --- /dev/null +++ b/pkg/storage/unified/resource/store.go @@ -0,0 +1,185 @@ +package resource + +import ( + "context" + "encoding/json" + "fmt" + "path/filepath" + "sort" + "strings" + + "github.com/hack-pad/hackpadfs" + "github.com/hack-pad/hackpadfs/mem" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +type FileSystemStoreOptions struct { + // OTel tracer + Tracer trace.Tracer + + // Get the next EventID. When not set, this will default to snowflake IDs + NextEventID func() int64 + + // Root file system -- null will be in memory + Root hackpadfs.FS +} + +func NewFSStore(opts FileSystemStoreOptions) (ResourceStoreServer, error) { + if opts.Tracer == nil { + opts.Tracer = noop.NewTracerProvider().Tracer("testing") + } + + var err error + root := opts.Root + if root == nil { + root, err = mem.NewFS() + if err != nil { + return nil, err + } + } + + store := &fsStore{root: root} + store.writer, err = NewResourceWriter(WriterOptions{ + Tracer: opts.Tracer, + Reader: store.Read, + Appender: store.append, + }) + + return store, err +} + +var _ ResourceStoreServer = &fsStore{} + +type fsStore struct { + writer ResourceWriter + + root hackpadfs.FS +} + +type fsEvent struct { + ResourceVersion int64 `json:"resourceVersion"` + Message string `json:"message,omitempty"` + Operation string `json:"operation,omitempty"` + Value json.RawMessage `json:"value,omitempty"` + BlobPath string `json:"blob,omitempty"` +} + +// The only write command +func (f *fsStore) append(ctx context.Context, event *WriteEvent) (int64, error) { + body := fsEvent{ + ResourceVersion: event.EventID, + Message: event.Message, + Operation: event.Operation.String(), + Value: event.Value, + // Blob... + } + // For this case, we will treat them the same + event.Key.ResourceVersion = 0 + dir := event.Key.NamespacedPath() + err := hackpadfs.MkdirAll(f.root, dir, 0750) + if err != nil { + return 0, err + } + + bytes, err := json.Marshal(&body) + if err != nil { + return 0, err + } + + fpath := filepath.Join(dir, fmt.Sprintf("%d.json", event.EventID)) + file, err := hackpadfs.OpenFile(f.root, fpath, hackpadfs.FlagWriteOnly|hackpadfs.FlagCreate, 0750) + if err != nil { + return 0, err + } + _, err = hackpadfs.WriteFile(file, bytes) + return event.EventID, err +} + +// Read implements ResourceStoreServer. +func (f *fsStore) Read(ctx context.Context, req *ReadRequest) (*ReadResponse, error) { + rv := req.Key.ResourceVersion + req.Key.ResourceVersion = 0 + + fname := "--x--" + dir := req.Key.NamespacedPath() + if rv > 0 { + fname = fmt.Sprintf("%d.json", rv) + } else { + files, err := hackpadfs.ReadDir(f.root, dir) + if err != nil { + return nil, err + } + + // Sort by name + sort.Slice(files, func(i, j int) bool { + a := files[i].Name() + b := files[j].Name() + return a > b // ?? should we parse the numbers ??? + }) + + // The first matching file + for _, v := range files { + fname = v.Name() + if strings.HasSuffix(fname, ".json") { + break + } + } + } + + evt, err := f.open(filepath.Join(dir, fname)) + if err != nil || evt.Operation == ResourceOperation_DELETED.String() { + return nil, apierrors.NewNotFound(schema.GroupResource{ + Group: req.Key.Group, + Resource: req.Key.Resource, + }, req.Key.Name) + } + + return &ReadResponse{ + ResourceVersion: evt.ResourceVersion, + Value: evt.Value, + Message: evt.Message, + }, nil +} + +func (f *fsStore) open(p string) (*fsEvent, error) { + raw, err := hackpadfs.ReadFile(f.root, p) + if err != nil { + return nil, err + } + + evt := &fsEvent{} + err = json.Unmarshal(raw, evt) + return evt, err +} + +func (f *fsStore) Create(ctx context.Context, req *CreateRequest) (*CreateResponse, error) { + return f.writer.Create(ctx, req) +} + +// Update implements ResourceStoreServer. +func (f *fsStore) Update(ctx context.Context, req *UpdateRequest) (*UpdateResponse, error) { + return f.writer.Update(ctx, req) +} + +// Delete implements ResourceStoreServer. +func (f *fsStore) Delete(ctx context.Context, req *DeleteRequest) (*DeleteResponse, error) { + return f.writer.Delete(ctx, req) +} + +// IsHealthy implements ResourceStoreServer. +func (f *fsStore) IsHealthy(context.Context, *HealthCheckRequest) (*HealthCheckResponse, error) { + return &HealthCheckResponse{Status: HealthCheckResponse_SERVING}, nil +} + +// List implements ResourceStoreServer. +func (f *fsStore) List(ctx context.Context, req *ListRequest) (*ListResponse, error) { + panic("unimplemented") +} + +// Watch implements ResourceStoreServer. +func (f *fsStore) Watch(*WatchRequest, ResourceStore_WatchServer) error { + panic("unimplemented") +} diff --git a/pkg/storage/unified/resource/writer.go b/pkg/storage/unified/resource/writer.go index 9f4792d2677..5a54349ebb2 100644 --- a/pkg/storage/unified/resource/writer.go +++ b/pkg/storage/unified/resource/writer.go @@ -10,6 +10,7 @@ import ( "github.com/bwmarrin/snowflake" "github.com/prometheus/client_golang/prometheus" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "go.opentelemetry.io/otel/trace" @@ -69,7 +70,8 @@ func NewResourceWriter(opts WriterOptions) (ResourceWriter, error) { if opts.NextEventID == nil { eventNode, err := snowflake.NewNode(opts.NodeID) if err != nil { - return nil, fmt.Errorf("error initializing snowflake id generator :: %w", err) + return nil, apierrors.NewInternalError( + fmt.Errorf("error initializing snowflake id generator :: %w", err)) } opts.NextEventID = func() int64 { return eventNode.Generate().Int64() @@ -112,45 +114,48 @@ func (s *writeServer) newEvent(ctx context.Context, key *ResourceKey, value, old obj, err := utils.MetaAccessor(dummy) if err != nil { - return nil, fmt.Errorf("invalid object in json") + return nil, apierrors.NewBadRequest("invalid object in json") } if obj.GetUID() == "" { - return nil, fmt.Errorf("the UID must be set") + return nil, apierrors.NewBadRequest("the UID must be set") } if obj.GetGenerateName() != "" { - return nil, fmt.Errorf("can not save value with generate name") + return nil, apierrors.NewBadRequest("can not save value with generate name") } gvk := obj.GetGroupVersionKind() if gvk.Kind == "" { - return nil, fmt.Errorf("expecting resources with a kind in the body") + return nil, apierrors.NewBadRequest("expecting resources with a kind in the body") } if gvk.Version == "" { - return nil, fmt.Errorf("expecting resources with an apiVersion") + return nil, apierrors.NewBadRequest("expecting resources with an apiVersion") } if gvk.Group != "" && gvk.Group != key.Group { - return nil, fmt.Errorf("group in key does not match group in the body (%s != %s)", key.Group, gvk.Group) + return nil, apierrors.NewBadRequest( + fmt.Sprintf("group in key does not match group in the body (%s != %s)", key.Group, gvk.Group), + ) } if obj.GetName() != key.Name { - return nil, fmt.Errorf("key name does not match the name in the body") + return nil, apierrors.NewBadRequest("key name does not match the name in the body") } if obj.GetNamespace() != key.Namespace { - return nil, fmt.Errorf("key namespace does not match the namespace in the body") + return nil, apierrors.NewBadRequest("key namespace does not match the namespace in the body") } folder := obj.GetFolder() if folder != "" { if s.opts.FolderAccess == nil { - return nil, fmt.Errorf("folders are not supported") + return nil, apierrors.NewBadRequest("folders are not supported") } else if !s.opts.FolderAccess(ctx, event.Requester, folder) { - return nil, fmt.Errorf("unable to add resource to folder") // 403? + return nil, apierrors.NewBadRequest("unable to add resource to folder") // 403? } } origin, err := obj.GetOriginInfo() if err != nil { - return nil, fmt.Errorf("invalid origin info") + return nil, apierrors.NewBadRequest("invalid origin info") } if origin != nil && s.opts.OriginAccess != nil { if !s.opts.OriginAccess(ctx, event.Requester, origin.Name) { - return nil, fmt.Errorf("not allowed to write resource to origin (%s)", origin.Name) + return nil, apierrors.NewBadRequest( + fmt.Sprintf("not allowed to write resource to origin (%s)", origin.Name)) } } event.Object = obj @@ -160,22 +165,25 @@ func (s *writeServer) newEvent(ctx context.Context, key *ResourceKey, value, old dummy := &dummyObject{} err = json.Unmarshal(oldValue, dummy) if err != nil { - return nil, fmt.Errorf("error reading old json value") + return nil, apierrors.NewBadRequest("error reading old json value") } old, err := utils.MetaAccessor(dummy) if err != nil { - return nil, fmt.Errorf("invalid object inside old json") + return nil, apierrors.NewBadRequest("invalid object inside old json") } if key.Name != old.GetName() { - return nil, fmt.Errorf("the old value has a different name (%s != %s)", key.Name, old.GetName()) + return nil, apierrors.NewBadRequest( + fmt.Sprintf("the old value has a different name (%s != %s)", key.Name, old.GetName())) } // Can not change creation timestamps+user if obj.GetCreatedBy() != old.GetCreatedBy() { - return nil, fmt.Errorf("can not change the created by metadata (%s != %s)", obj.GetCreatedBy(), old.GetCreatedBy()) + return nil, apierrors.NewBadRequest( + fmt.Sprintf("can not change the created by metadata (%s != %s)", obj.GetCreatedBy(), old.GetCreatedBy())) } if obj.GetCreationTimestamp() != old.GetCreationTimestamp() { - return nil, fmt.Errorf("can not change the CreationTimestamp metadata (%v != %v)", obj.GetCreationTimestamp(), old.GetCreationTimestamp()) + return nil, apierrors.NewBadRequest( + fmt.Sprintf("can not change the CreationTimestamp metadata (%v != %v)", obj.GetCreationTimestamp(), old.GetCreationTimestamp())) } oldFolder := obj.GetFolder() @@ -194,7 +202,7 @@ func (s *writeServer) Create(ctx context.Context, req *CreateRequest) (*CreateRe defer span.End() if req.Key.ResourceVersion > 0 { - return nil, fmt.Errorf("can not update a specific resource version") + return nil, apierrors.NewBadRequest("can not update a specific resource version") } event, err := s.newEvent(ctx, req.Key, req.Value, nil) @@ -203,27 +211,28 @@ func (s *writeServer) Create(ctx context.Context, req *CreateRequest) (*CreateRe } event.Operation = ResourceOperation_CREATED event.Blob = req.Blob + event.Message = req.Message rsp := &CreateResponse{} // Make sure the created by user is accurate //---------------------------------------- val := event.Object.GetCreatedBy() if val != "" && val != event.Requester.GetUID().String() { - return nil, fmt.Errorf("created by annotation does not match: metadata.annotations#" + utils.AnnoKeyCreatedBy) + return nil, apierrors.NewBadRequest("created by annotation does not match: metadata.annotations#" + utils.AnnoKeyCreatedBy) } // Create can not have updated properties //---------------------------------------- if event.Object.GetUpdatedBy() != "" { - return nil, fmt.Errorf("unexpected metadata.annotations#" + utils.AnnoKeyCreatedBy) + return nil, apierrors.NewBadRequest("unexpected metadata.annotations#" + utils.AnnoKeyCreatedBy) } ts, err := event.Object.GetUpdatedTimestamp() if err != nil { - return nil, fmt.Errorf(fmt.Sprintf("invalid timestamp: %s", err)) + return nil, apierrors.NewBadRequest(fmt.Sprintf("invalid timestamp: %s", err)) } if ts != nil { - return nil, fmt.Errorf("unexpected metadata.annotations#" + utils.AnnoKeyUpdatedTimestamp) + return nil, apierrors.NewBadRequest("unexpected metadata.annotations#" + utils.AnnoKeyUpdatedTimestamp) } // Append and set the resource version @@ -238,7 +247,7 @@ func (s *writeServer) Update(ctx context.Context, req *UpdateRequest) (*UpdateRe rsp := &UpdateResponse{} if req.Key.ResourceVersion < 0 { - return nil, fmt.Errorf("update must include the previous version") + return nil, apierrors.NewBadRequest("update must include the previous version") } latest, err := s.opts.Reader(ctx, &ReadRequest{ @@ -248,7 +257,7 @@ func (s *writeServer) Update(ctx context.Context, req *UpdateRequest) (*UpdateRe return nil, err } if latest.Value == nil { - return nil, fmt.Errorf("current value does not exist") + return nil, apierrors.NewBadRequest("current value does not exist") } event, err := s.newEvent(ctx, req.Key, req.Value, latest.Value) @@ -257,12 +266,13 @@ func (s *writeServer) Update(ctx context.Context, req *UpdateRequest) (*UpdateRe } event.Operation = ResourceOperation_UPDATED event.PreviousRV = latest.ResourceVersion + event.Message = req.Message // Make sure the update user is accurate //---------------------------------------- val := event.Object.GetUpdatedBy() if val != "" && val != event.Requester.GetUID().String() { - return nil, fmt.Errorf("updated by annotation does not match: metadata.annotations#" + utils.AnnoKeyUpdatedBy) + return nil, apierrors.NewBadRequest("updated by annotation does not match: metadata.annotations#" + utils.AnnoKeyUpdatedBy) } rsp.ResourceVersion, err = s.opts.Appender(ctx, event) @@ -276,7 +286,7 @@ func (s *writeServer) Delete(ctx context.Context, req *DeleteRequest) (*DeleteRe rsp := &DeleteResponse{} if req.Key.ResourceVersion < 0 { - return nil, fmt.Errorf("update must include the previous version") + return nil, apierrors.NewBadRequest("update must include the previous version") } latest, err := s.opts.Reader(ctx, &ReadRequest{ @@ -286,7 +296,7 @@ func (s *writeServer) Delete(ctx context.Context, req *DeleteRequest) (*DeleteRe return nil, err } if latest.ResourceVersion != req.Key.ResourceVersion { - return nil, fmt.Errorf("deletion request does not match current revision (%d != %d)", req.Key.ResourceVersion, latest.ResourceVersion) + return nil, ErrOptimisticLockingFailed } now := metav1.NewTime(time.Now()) @@ -298,12 +308,13 @@ func (s *writeServer) Delete(ctx context.Context, req *DeleteRequest) (*DeleteRe } event.Requester, err = identity.GetRequester(ctx) if err != nil { - return nil, fmt.Errorf("unable to get user") + return nil, apierrors.NewBadRequest("unable to get user") } marker := &DeletedMarker{} err = json.Unmarshal(latest.Value, marker) if err != nil { - return nil, fmt.Errorf("unable to read previous object, %w", err) + return nil, apierrors.NewBadRequest( + fmt.Sprintf("unable to read previous object, %v", err)) } event.Object, err = utils.MetaAccessor(marker) if err != nil { @@ -321,7 +332,8 @@ func (s *writeServer) Delete(ctx context.Context, req *DeleteRequest) (*DeleteRe marker.Annotations["RestoreResourceVersion"] = fmt.Sprintf("%d", event.PreviousRV) event.Value, err = json.Marshal(marker) if err != nil { - return nil, fmt.Errorf("unable creating deletion marker, %w", err) + return nil, apierrors.NewBadRequest( + fmt.Sprintf("unable creating deletion marker, %v", err)) } rsp.ResourceVersion, err = s.opts.Appender(ctx, event) diff --git a/pkg/storage/unified/resource/writer_test.go b/pkg/storage/unified/resource/writer_test.go index 55db3bfff6c..57eec433156 100644 --- a/pkg/storage/unified/resource/writer_test.go +++ b/pkg/storage/unified/resource/writer_test.go @@ -4,11 +4,14 @@ import ( "context" "embed" "encoding/json" + "fmt" + "os" "testing" "time" + "github.com/hack-pad/hackpadfs" + hackos "github.com/hack-pad/hackpadfs/os" "github.com/stretchr/testify/require" - "go.opentelemetry.io/otel/trace/noop" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "github.com/grafana/grafana/pkg/apimachinery/identity" @@ -16,7 +19,6 @@ import ( ) func TestWriter(t *testing.T) { - tracer := noop.NewTracerProvider().Tracer("testing") testUserA := &identity.StaticRequester{ Namespace: identity.NamespaceUser, UserID: 123, @@ -26,11 +28,17 @@ func TestWriter(t *testing.T) { } ctx := identity.WithRequester(context.Background(), testUserA) - store := NewMemoryStore() - writer, err := NewResourceWriter(WriterOptions{ - Tracer: tracer, - Reader: store.Read, - Appender: store.WriteEvent, + var root hackpadfs.FS + if false { + tmp, err := os.MkdirTemp("", "xxx-*") + require.NoError(t, err) + + root, err = hackos.NewFS().Sub(tmp[1:]) + require.NoError(t, err) + fmt.Printf("ROOT: %s\n\n", tmp) + } + store, err := NewFSStore(FileSystemStoreOptions{ + Root: root, }) require.NoError(t, err) @@ -42,7 +50,7 @@ func TestWriter(t *testing.T) { Namespace: "default", Name: "fdgsv37qslr0ga", } - created, err := writer.Create(ctx, &CreateRequest{ + created, err := store.Create(ctx, &CreateRequest{ Value: raw, Key: key, }) @@ -69,7 +77,7 @@ func TestWriter(t *testing.T) { require.NoError(t, err) key.ResourceVersion = created.ResourceVersion - updated, err := writer.Update(ctx, &UpdateRequest{Key: key, Value: raw}) + updated, err := store.Update(ctx, &UpdateRequest{Key: key, Value: raw}) require.NoError(t, err) require.True(t, updated.ResourceVersion > created.ResourceVersion) @@ -80,15 +88,15 @@ func TestWriter(t *testing.T) { require.Equal(t, updated.ResourceVersion, found.ResourceVersion) key.ResourceVersion = updated.ResourceVersion - deleted, err := writer.Delete(ctx, &DeleteRequest{Key: key}) + deleted, err := store.Delete(ctx, &DeleteRequest{Key: key}) require.NoError(t, err) require.True(t, deleted.ResourceVersion > updated.ResourceVersion) // We should get not found when trying to read the latest value key.ResourceVersion = 0 - found, _ = store.Read(ctx, &ReadRequest{Key: key}) - require.Equal(t, int32(404), found.Status.Code) - require.Nil(t, found.Value) + found, err = store.Read(ctx, &ReadRequest{Key: key}) + require.Error(t, err) + require.Nil(t, found) }) }