Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions cmd/cachewd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package main

import (
"context"
"fmt"
"log/slog"
"net"
"net/http"
"os"
"slices"
"strings"
"time"

"github.com/alecthomas/hcl/v2"
"github.com/alecthomas/kong"

"github.com/block/cachew/internal/config"
Expand All @@ -18,6 +21,8 @@ import (
)

var cli struct {
Schema bool `help:"Print the configuration file schema." xor:"command"`

Config *os.File `hcl:"-" help:"Configuration file path." placeholder:"PATH" required:"" default:"cachew.hcl"`
Bind string `hcl:"bind" default:"127.0.0.1:8080" help:"Bind address for the server."`
SchedulerConfig jobscheduler.Config `embed:"" prefix:"scheduler-"`
Expand All @@ -30,6 +35,18 @@ func main() {
ctx := context.Background()
logger, ctx := logging.Configure(ctx, cli.LoggingConfig)

switch {
case cli.Schema:
schema := config.Schema()
slices.SortStableFunc(schema.Entries, func(a, b hcl.Entry) int {
return strings.Compare(a.EntryKey(), b.EntryKey())
})
text, err := hcl.MarshalAST(schema)
kctx.FatalIfErrorf(err)
fmt.Printf("%s\n", text)
return
}

mux := http.NewServeMux()

scheduler := jobscheduler.New(ctx, cli.SchedulerConfig)
Expand Down
44 changes: 34 additions & 10 deletions internal/cache/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,52 @@ import (
// ErrNotFound is returned when a cache backend is not found.
var ErrNotFound = errors.New("cache backend not found")

var registry = map[string]func(ctx context.Context, config *hcl.Block) (Cache, error){}
type registryEntry struct {
schema *hcl.Block
factory func(ctx context.Context, config *hcl.Block) (Cache, error)
}

var registry = map[string]registryEntry{}

// Factory is a function that creates a new cache instance from the given hcl-tagged configuration struct.
type Factory[Config any, C Cache] func(ctx context.Context, config Config) (C, error)

// Register a cache factory function.
func Register[Config any, C Cache](id string, factory Factory[Config, C]) {
registry[id] = func(ctx context.Context, config *hcl.Block) (Cache, error) {
var cfg Config
if err := hcl.UnmarshalBlock(config, &cfg); err != nil {
return nil, errors.WithStack(err)
}
return factory(ctx, cfg)
func Register[Config any, C Cache](id, description string, factory Factory[Config, C]) {
var c Config
schema, err := hcl.BlockSchema(id, &c)
if err != nil {
panic(err)
}
block := schema.Entries[0].(*hcl.Block)
block.Comments = hcl.CommentList{description}
registry[id] = registryEntry{
schema: block,
factory: func(ctx context.Context, config *hcl.Block) (Cache, error) {
var cfg Config
if err := hcl.UnmarshalBlock(config, &cfg); err != nil {
return nil, errors.WithStack(err)
}
return factory(ctx, cfg)
},
}
}

// Schema returns the schema for all registered cache backends.
func Schema() *hcl.AST {
ast := &hcl.AST{}
for _, entry := range registry {
ast.Entries = append(ast.Entries, entry.schema)
}
return ast
}

// Create a new cache instance from the given name and configuration.
//
// Will return "ErrNotFound" if the cache backend is not found.
func Create(ctx context.Context, name string, config *hcl.Block) (Cache, error) {
if factory, ok := registry[name]; ok {
return errors.WithStack2(factory(ctx, config))
if entry, ok := registry[name]; ok {
return errors.WithStack2(entry.factory(ctx, config))
}
return nil, errors.Errorf("%s: %w", name, ErrNotFound)
}
Expand Down
6 changes: 5 additions & 1 deletion internal/cache/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ import (
)

func init() {
Register("disk", NewDisk)
Register(
"disk",
"Caches objects on local disk, with a maximum size limit and LRU eviction",
NewDisk,
)
}

type DiskConfig struct {
Expand Down
6 changes: 5 additions & 1 deletion internal/cache/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import (
)

func init() {
Register("memory", NewMemory)
Register(
"memory",
"Caches objects in memory, with a maximum size limit and LRU eviction",
NewMemory,
)
}

type MemoryConfig struct {
Expand Down
6 changes: 5 additions & 1 deletion internal/cache/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import (
)

func init() {
Register("s3", NewS3)
Register(
"s3",
"Caches objects in S3",
NewS3,
)
}

type S3Config struct {
Expand Down
7 changes: 7 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ func (l *loggingMux) HandleFunc(pattern string, handler func(http.ResponseWriter

var _ strategy.Mux = (*loggingMux)(nil)

// Schema returns the configuration file schema.
func Schema() *hcl.AST {
return &hcl.AST{
Entries: append(strategy.Schema().Entries, cache.Schema().Entries...),
}
}

// Load HCL configuration and uses that to construct the cache backend, and proxy strategies.
func Load(ctx context.Context, r io.Reader, scheduler jobscheduler.Scheduler, mux *http.ServeMux, vars map[string]string) error {
logger := logging.FromContext(ctx)
Expand Down
44 changes: 34 additions & 10 deletions internal/strategy/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,43 @@ type Mux interface {
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
}

var registry = map[string]func(ctx context.Context, config *hcl.Block, scheduler jobscheduler.Scheduler, cache cache.Cache, mux Mux) (Strategy, error){}
type registryEntry struct {
schema *hcl.Block
factory func(ctx context.Context, config *hcl.Block, scheduler jobscheduler.Scheduler, cache cache.Cache, mux Mux) (Strategy, error)
}

var registry = map[string]registryEntry{}

type Factory[Config any, S Strategy] func(ctx context.Context, config Config, scheduler jobscheduler.Scheduler, cache cache.Cache, mux Mux) (S, error)

// Register a new proxy strategy.
func Register[Config any, S Strategy](id string, factory Factory[Config, S]) {
registry[id] = func(ctx context.Context, config *hcl.Block, scheduler jobscheduler.Scheduler, cache cache.Cache, mux Mux) (Strategy, error) {
var cfg Config
if err := hcl.UnmarshalBlock(config, &cfg, hcl.AllowExtra(false)); err != nil {
return nil, errors.WithStack(err)
}
return factory(ctx, cfg, scheduler, cache, mux)
func Register[Config any, S Strategy](id, description string, factory Factory[Config, S]) {
var c Config
schema, err := hcl.BlockSchema(id, &c)
if err != nil {
panic(err)
}
block := schema.Entries[0].(*hcl.Block)
block.Comments = hcl.CommentList{description}
registry[id] = registryEntry{
schema: block,
factory: func(ctx context.Context, config *hcl.Block, scheduler jobscheduler.Scheduler, cache cache.Cache, mux Mux) (Strategy, error) {
var cfg Config
if err := hcl.UnmarshalBlock(config, &cfg, hcl.AllowExtra(false)); err != nil {
return nil, errors.WithStack(err)
}
return factory(ctx, cfg, scheduler, cache, mux)
},
}
}

// Schema returns the schema for all registered strategies.
func Schema() *hcl.AST {
ast := &hcl.AST{}
for _, entry := range registry {
ast.Entries = append(ast.Entries, entry.schema)
}
return ast
}

// Create a new proxy strategy.
Expand All @@ -46,8 +70,8 @@ func Create(
cache cache.Cache,
mux Mux,
) (Strategy, error) {
if factory, ok := registry[name]; ok {
return errors.WithStack2(factory(ctx, config, scheduler.WithQueuePrefix(name), cache, mux))
if entry, ok := registry[name]; ok {
return errors.WithStack2(entry.factory(ctx, config, scheduler.WithQueuePrefix(name), cache, mux))
}
return nil, errors.Errorf("%s: %w", name, ErrNotFound)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/strategy/apiv1.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
)

func init() {
Register("apiv1", NewAPIV1)
Register("apiv1", "The stable API of the cache server.", NewAPIV1)
}

var _ Strategy = (*APIV1)(nil)
Expand Down
2 changes: 1 addition & 1 deletion internal/strategy/artifactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

func init() {
Register("artifactory", NewArtifactory)
Register("artifactory", "Caches artifacts from an Artifactory server.", NewArtifactory)
}

// ArtifactoryConfig represents the configuration for the Artifactory strategy.
Expand Down
2 changes: 1 addition & 1 deletion internal/strategy/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (
)

func init() {
strategy.Register("git", New)
strategy.Register("git", "Caches Git repositories, including bundle and tarball snapshots.", New)
}

type Config struct {
Expand Down
2 changes: 1 addition & 1 deletion internal/strategy/github_releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import (
)

func init() {
Register("github-releases", NewGitHubReleases)
Register("github-releases", "Caches public and authenticated GitHub releases.", NewGitHubReleases)
}

type GitHubReleasesConfig struct {
Expand Down
2 changes: 1 addition & 1 deletion internal/strategy/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
)

func init() {
Register("host", NewHost)
Register("host", "A generic host-based proxying strategy.", NewHost)
}

// HostConfig represents the configuration for the Host strategy.
Expand Down