feat: persist cache effective upstream path
This commit is contained in:
75
internal/cache/fs_store.go
vendored
75
internal/cache/fs_store.go
vendored
@@ -2,6 +2,7 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@@ -49,6 +50,10 @@ type entryLock struct {
|
||||
refs int
|
||||
}
|
||||
|
||||
type entryMetadata struct {
|
||||
EffectiveUpstreamPath string `json:"effective_upstream_path,omitempty"`
|
||||
}
|
||||
|
||||
func (s *fileStore) Get(ctx context.Context, locator Locator) (*ReadResult, error) {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
@@ -86,6 +91,12 @@ func (s *fileStore) Get(ctx context.Context, locator Locator) (*ReadResult, erro
|
||||
SizeBytes: info.Size(),
|
||||
ModTime: info.ModTime(),
|
||||
}
|
||||
if metadata, err := s.readMetadata(filePath); err == nil {
|
||||
entry.EffectiveUpstreamPath = metadata.EffectiveUpstreamPath
|
||||
} else if !errors.Is(err, fs.ErrNotExist) {
|
||||
file.Close()
|
||||
return nil, err
|
||||
}
|
||||
return &ReadResult{Entry: entry, Reader: file}, nil
|
||||
}
|
||||
|
||||
@@ -133,12 +144,16 @@ func (s *fileStore) Put(ctx context.Context, locator Locator, body io.Reader, op
|
||||
if err := os.Chtimes(filePath, modTime, modTime); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.writeMetadata(filePath, opts.EffectiveUpstreamPath); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
entry := Entry{
|
||||
Locator: locator,
|
||||
FilePath: filePath,
|
||||
SizeBytes: written,
|
||||
ModTime: modTime,
|
||||
Locator: locator,
|
||||
FilePath: filePath,
|
||||
SizeBytes: written,
|
||||
ModTime: modTime,
|
||||
EffectiveUpstreamPath: opts.EffectiveUpstreamPath,
|
||||
}
|
||||
return &entry, nil
|
||||
}
|
||||
@@ -157,6 +172,54 @@ func (s *fileStore) Remove(ctx context.Context, locator Locator) error {
|
||||
if err := os.Remove(filePath); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
if err := os.Remove(metadataPath(filePath)); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *fileStore) readMetadata(filePath string) (entryMetadata, error) {
|
||||
raw, err := os.ReadFile(metadataPath(filePath))
|
||||
if err != nil {
|
||||
return entryMetadata{}, err
|
||||
}
|
||||
var metadata entryMetadata
|
||||
if err := json.Unmarshal(raw, &metadata); err != nil {
|
||||
return entryMetadata{}, err
|
||||
}
|
||||
return metadata, nil
|
||||
}
|
||||
|
||||
func (s *fileStore) writeMetadata(filePath string, effectiveUpstreamPath string) error {
|
||||
metaFilePath := metadataPath(filePath)
|
||||
if effectiveUpstreamPath == "" {
|
||||
if err := os.Remove(metaFilePath); err != nil && !errors.Is(err, fs.ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
data, err := json.Marshal(entryMetadata{EffectiveUpstreamPath: effectiveUpstreamPath})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempFile, err := os.CreateTemp(filepath.Dir(metaFilePath), ".cache-meta-*")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tempName := tempFile.Name()
|
||||
if _, err := tempFile.Write(data); err != nil {
|
||||
tempFile.Close()
|
||||
_ = os.Remove(tempName)
|
||||
return err
|
||||
}
|
||||
if err := tempFile.Close(); err != nil {
|
||||
_ = os.Remove(tempName)
|
||||
return err
|
||||
}
|
||||
if err := os.Rename(tempName, metaFilePath); err != nil {
|
||||
_ = os.Remove(tempName)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -248,3 +311,7 @@ func copyWithContext(ctx context.Context, dst io.Writer, src io.Reader) (int64,
|
||||
func locatorKey(locator Locator) string {
|
||||
return locator.HubName + "::" + locator.Path
|
||||
}
|
||||
|
||||
func metadataPath(filePath string) string {
|
||||
return filePath + ".meta"
|
||||
}
|
||||
|
||||
12
internal/cache/store.go
vendored
12
internal/cache/store.go
vendored
@@ -26,7 +26,8 @@ type Store interface {
|
||||
|
||||
// PutOptions 控制写入过程中的可选属性。
|
||||
type PutOptions struct {
|
||||
ModTime time.Time
|
||||
ModTime time.Time
|
||||
EffectiveUpstreamPath string
|
||||
}
|
||||
|
||||
// Locator 唯一定位一个缓存条目(Hub + 相对路径),所有路径均为 URL 路径风格。
|
||||
@@ -37,10 +38,11 @@ type Locator struct {
|
||||
|
||||
// Entry 表示一次缓存命中结果,包含绝对文件路径及文件信息。
|
||||
type Entry struct {
|
||||
Locator Locator `json:"locator"`
|
||||
FilePath string `json:"file_path"`
|
||||
SizeBytes int64 `json:"size_bytes"`
|
||||
ModTime time.Time
|
||||
Locator Locator `json:"locator"`
|
||||
FilePath string `json:"file_path"`
|
||||
SizeBytes int64 `json:"size_bytes"`
|
||||
ModTime time.Time `json:"mod_time"`
|
||||
EffectiveUpstreamPath string `json:"effective_upstream_path,omitempty"`
|
||||
}
|
||||
|
||||
// ReadResult 组合 Entry 与正文 Reader,便于代理层直接将 Body 流式返回。
|
||||
|
||||
23
internal/cache/store_test.go
vendored
23
internal/cache/store_test.go
vendored
@@ -5,6 +5,7 @@ import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -62,6 +63,28 @@ func TestStoreRemove(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorePersistsEffectiveUpstreamPath(t *testing.T) {
|
||||
store := newTestStore(t)
|
||||
locator := Locator{HubName: "docker", Path: "/v2/coredns/manifests/v1.13.1"}
|
||||
|
||||
_, err := store.Put(context.Background(), locator, strings.NewReader("body"), PutOptions{
|
||||
EffectiveUpstreamPath: "/v2/coredns/coredns/manifests/v1.13.1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("put error: %v", err)
|
||||
}
|
||||
|
||||
result, err := store.Get(context.Background(), locator)
|
||||
if err != nil {
|
||||
t.Fatalf("get error: %v", err)
|
||||
}
|
||||
defer result.Reader.Close()
|
||||
|
||||
if result.Entry.EffectiveUpstreamPath != "/v2/coredns/coredns/manifests/v1.13.1" {
|
||||
t.Fatalf("unexpected effective upstream path: %q", result.Entry.EffectiveUpstreamPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreIgnoresDirectories(t *testing.T) {
|
||||
store := newTestStore(t)
|
||||
locator := Locator{HubName: "ghcr", Path: "/v2"}
|
||||
|
||||
Reference in New Issue
Block a user