From 2ca07d9a1748cfb876fd439aff43368b999a1f97 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Mon, 30 Mar 2026 20:14:07 +1100 Subject: [PATCH] feat: require metadata backend and add constructor logging Refuse to start if no metadata block is configured in HCL, matching the existing validation for cache backends. Add InfoContext logging to the memory and S3 metadata backend constructors, consistent with cache backends. Co-Authored-By: Claude Opus 4.6 (1M context) --- cachew.hcl | 2 ++ internal/config/config.go | 20 ++++++++++---------- internal/config/config_test.go | 24 ++++++++++++++++++++++++ internal/metadatadb/memory.go | 5 ++++- internal/metadatadb/s3.go | 3 +++ 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/cachew.hcl b/cachew.hcl index aad29db..e58eeb7 100644 --- a/cachew.hcl +++ b/cachew.hcl @@ -57,3 +57,5 @@ cache disk { limit-mb = 250000 max-ttl = "8h" } + +metadata memory {} diff --git a/internal/config/config.go b/internal/config/config.go index 1a95f21..d691f01 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -179,16 +179,16 @@ func Load( return nil, nil, errors.Errorf("%s: expected at least one cache backend", ast.Pos) } - var metadata metadatadb.Backend - if classified.metadata != nil { - name, inner, err := unwrapBlock(classified.metadata) - if err != nil { - return nil, nil, err - } - metadata, err = mr.Create(ctx, name, inner, vars) - if err != nil { - return nil, nil, errors.Errorf("%s: %w", classified.metadata.Pos, err) - } + if classified.metadata == nil { + return nil, nil, errors.Errorf("%s: expected a metadata backend", ast.Pos) + } + metaName, metaInner, err := unwrapBlock(classified.metadata) + if err != nil { + return nil, nil, err + } + metadata, err := mr.Create(ctx, metaName, metaInner, vars) + if err != nil { + return nil, nil, errors.Errorf("%s: %w", classified.metadata.Pos, err) } cache := cache.MaybeNewTiered(ctx, caches) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 4ef228e..dc1aad5 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -1,11 +1,19 @@ package config //nolint:testpackage import ( + "context" + "log/slog" + "net/http" "strings" "testing" "github.com/alecthomas/assert/v2" "github.com/alecthomas/hcl/v2" + + "github.com/block/cachew/internal/cache" + "github.com/block/cachew/internal/logging" + "github.com/block/cachew/internal/metadatadb" + "github.com/block/cachew/internal/strategy" ) func TestUnwrapBlock(t *testing.T) { @@ -175,3 +183,19 @@ git-clone { }) } } + +func TestLoadRequiresMetadataBackend(t *testing.T) { + cr := cache.NewRegistry() + cache.RegisterMemory(cr) + mr := metadatadb.NewRegistry() + metadatadb.RegisterMemory(mr) + sr := strategy.NewRegistry() + + ast, err := hcl.Parse(strings.NewReader(`cache memory {}`)) + assert.NoError(t, err) + + ctx := logging.ContextWithLogger(context.Background(), slog.Default()) + _, _, err = Load(ctx, cr, mr, sr, ast, http.NewServeMux(), nil) + assert.Error(t, err) + assert.Contains(t, err.Error(), "expected a metadata backend") +} diff --git a/internal/metadatadb/memory.go b/internal/metadatadb/memory.go index 9bcc41b..9a32a37 100644 --- a/internal/metadatadb/memory.go +++ b/internal/metadatadb/memory.go @@ -6,12 +6,15 @@ import ( "sync" "github.com/alecthomas/errors" + + "github.com/block/cachew/internal/logging" ) // RegisterMemory registers the in-memory metadata backend. func RegisterMemory(r *Registry) { Register(r, "memory", "In-memory metadata store for testing and single-instance deployments", - func(_ context.Context, _ MemoryConfig) (*MemoryBackend, error) { + func(ctx context.Context, _ MemoryConfig) (*MemoryBackend, error) { + logging.FromContext(ctx).InfoContext(ctx, "Constructing in-memory metadata backend") return NewMemoryBackend(), nil }, ) diff --git a/internal/metadatadb/s3.go b/internal/metadatadb/s3.go index c49de4b..cc87973 100644 --- a/internal/metadatadb/s3.go +++ b/internal/metadatadb/s3.go @@ -71,6 +71,9 @@ func NewS3Backend(ctx context.Context, clientProvider s3client.ClientProvider, c return nil, errors.Errorf("bucket %s does not exist", config.Bucket) } + logging.FromContext(ctx).InfoContext(ctx, "Constructing S3 metadata backend", + "bucket", config.Bucket, "prefix", config.Prefix, "lock-ttl", config.LockTTL, "sync-interval", config.SyncInterval) + ctx, cancel := context.WithCancel(ctx) return &S3Backend{ client: client,