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
21 changes: 17 additions & 4 deletions cmd/cachewd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ import (
"github.com/block/cachew/internal/jobscheduler"
"github.com/block/cachew/internal/logging"
"github.com/block/cachew/internal/metrics"
"github.com/block/cachew/internal/strategy"
"github.com/block/cachew/internal/strategy/git"
"github.com/block/cachew/internal/strategy/gomod"
)

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."`
URL string `hcl:"url" default:"http://127.0.0.1:8080/" help:"Base URL for cachewd."`
SchedulerConfig jobscheduler.Config `embed:"" prefix:"scheduler-"`
LoggingConfig logging.Config `embed:"" prefix:"log-"`
MetricsConfig metrics.Config `embed:"" prefix:"metrics-"`
Expand All @@ -41,15 +45,26 @@ func main() {
ctx := context.Background()
logger, ctx := logging.Configure(ctx, cli.LoggingConfig)

scheduler := jobscheduler.New(ctx, cli.SchedulerConfig)

cr := cache.NewRegistry()
cache.RegisterMemory(cr)
cache.RegisterDisk(cr)
cache.RegisterS3(cr)

sr := strategy.NewRegistry()
strategy.RegisterAPIV1(sr)
strategy.RegisterArtifactory(sr)
strategy.RegisterGitHubReleases(sr)
strategy.RegisterHermit(sr, cli.URL)
strategy.RegisterHost(sr)
git.Register(sr, scheduler)
gomod.Register(sr)

// Commands
switch { //nolint:gocritic
case cli.Schema:
schema := config.Schema(cr)
schema := config.Schema(cr, sr)
slices.SortStableFunc(schema.Entries, func(a, b hcl.Entry) int {
return strings.Compare(a.EntryKey(), b.EntryKey())
})
Expand Down Expand Up @@ -78,9 +93,7 @@ func main() {
_, _ = w.Write([]byte("OK")) //nolint:errcheck
})

scheduler := jobscheduler.New(ctx, cli.SchedulerConfig)

err := config.Load(ctx, cr, cli.Config, scheduler, mux, parseEnvars())
err := config.Load(ctx, cr, sr, cli.Config, mux, parseEnvars())
kctx.FatalIfErrorf(err)

metricsClient, err := metrics.New(ctx, cli.MetricsConfig)
Expand Down
5 changes: 2 additions & 3 deletions internal/cache/remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

"github.com/block/cachew/internal/cache"
"github.com/block/cachew/internal/cache/cachetest"
"github.com/block/cachew/internal/jobscheduler"
"github.com/block/cachew/internal/logging"
"github.com/block/cachew/internal/strategy"
)
Expand All @@ -28,7 +27,7 @@ func TestRemoteCache(t *testing.T) {
t.Cleanup(func() { memCache.Close() })

mux := http.NewServeMux()
_, err = strategy.NewAPIV1(ctx, struct{}{}, jobscheduler.New(ctx, jobscheduler.Config{}), memCache, mux)
_, err = strategy.NewAPIV1(ctx, struct{}{}, memCache, mux)
assert.NoError(t, err)
ts := httptest.NewServer(mux)
t.Cleanup(ts.Close)
Expand All @@ -53,7 +52,7 @@ func TestRemoteCacheSoak(t *testing.T) {
defer memCache.Close()

mux := http.NewServeMux()
_, err = strategy.NewAPIV1(ctx, struct{}{}, jobscheduler.New(ctx, jobscheduler.Config{}), memCache, mux)
_, err = strategy.NewAPIV1(ctx, struct{}{}, memCache, mux)
assert.NoError(t, err)
ts := httptest.NewServer(mux)
defer ts.Close()
Expand Down
16 changes: 11 additions & 5 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"github.com/alecthomas/hcl/v2"

"github.com/block/cachew/internal/cache"
"github.com/block/cachew/internal/jobscheduler"
"github.com/block/cachew/internal/logging"
"github.com/block/cachew/internal/strategy"
_ "github.com/block/cachew/internal/strategy/git" // Register git strategy
Expand All @@ -37,14 +36,21 @@ func (l *loggingMux) HandleFunc(pattern string, handler func(http.ResponseWriter
var _ strategy.Mux = (*loggingMux)(nil)

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

// Load HCL configuration and uses that to construct the cache backend, and proxy strategies.
func Load(ctx context.Context, cr *cache.Registry, r io.Reader, scheduler jobscheduler.Scheduler, mux *http.ServeMux, vars map[string]string) error {
func Load(
ctx context.Context,
cr *cache.Registry,
sr *strategy.Registry,
r io.Reader,
mux *http.ServeMux,
vars map[string]string,
) error {
logger := logging.FromContext(ctx)
ast, err := hcl.Parse(r)
if err != nil {
Expand Down Expand Up @@ -88,7 +94,7 @@ func Load(ctx context.Context, cr *cache.Registry, r io.Reader, scheduler jobsch
for _, block := range strategyCandidates {
logger := logger.With("strategy", block.Name)
mlog := &loggingMux{logger: logger, mux: mux}
_, err := strategy.Create(ctx, block.Name, block, scheduler.WithQueuePrefix(block.Name), cache, mlog, vars)
_, err := sr.Create(ctx, block.Name, block, cache, mlog, vars)
if err != nil {
return errors.Errorf("%s: %w", block.Pos, err)
}
Expand Down
36 changes: 21 additions & 15 deletions internal/strategy/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/alecthomas/hcl/v2"

"github.com/block/cachew/internal/cache"
"github.com/block/cachew/internal/jobscheduler"
)

// ErrNotFound is returned when a strategy is not found.
Expand All @@ -21,43 +20,51 @@ type Mux interface {
HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
}

type Registry struct {
registry map[string]registryEntry
}

func NewRegistry() *Registry {
return &Registry{
registry: make(map[string]registryEntry),
}
}

type registryEntry struct {
schema *hcl.Block
factory func(ctx context.Context, config *hcl.Block, scheduler jobscheduler.Scheduler, cache cache.Cache, mux Mux, vars map[string]string) (Strategy, error)
factory func(ctx context.Context, config *hcl.Block, cache cache.Cache, mux Mux, vars map[string]string) (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)
type Factory[Config any, S Strategy] func(ctx context.Context, config Config, cache cache.Cache, mux Mux) (S, error)

// Register a new proxy strategy.
func Register[Config any, S Strategy](id, description string, factory Factory[Config, S]) {
func Register[Config any, S Strategy](r *Registry, 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) //nolint:errcheck // This seems spurious
block.Comments = hcl.CommentList{description}
registry[id] = registryEntry{
r.registry[id] = registryEntry{
schema: block,
factory: func(ctx context.Context, config *hcl.Block, scheduler jobscheduler.Scheduler, cache cache.Cache, mux Mux, vars map[string]string) (Strategy, error) {
factory: func(ctx context.Context, config *hcl.Block, cache cache.Cache, mux Mux, vars map[string]string) (Strategy, error) {
var cfg Config
transformer := func(defaultValue string) string {
return os.Expand(defaultValue, func(key string) string { return vars[key] })
}
if err := hcl.UnmarshalBlock(config, &cfg, hcl.AllowExtra(false), hcl.WithDefaultTransformer(transformer)); err != nil {
return nil, errors.WithStack(err)
}
return factory(ctx, cfg, scheduler, cache, mux)
return factory(ctx, cfg, cache, mux)
},
}
}

// Schema returns the schema for all registered strategies.
func Schema() *hcl.AST {
func (r *Registry) Schema() *hcl.AST {
ast := &hcl.AST{}
for _, entry := range registry {
for _, entry := range r.registry {
ast.Entries = append(ast.Entries, entry.schema)
}
return ast
Expand All @@ -66,17 +73,16 @@ func Schema() *hcl.AST {
// Create a new proxy strategy.
//
// Will return "ErrNotFound" if the strategy is not found.
func Create(
func (r *Registry) Create(
ctx context.Context,
name string,
config *hcl.Block,
scheduler jobscheduler.Scheduler,
cache cache.Cache,
mux Mux,
vars map[string]string,
) (Strategy, error) {
if entry, ok := registry[name]; ok {
return errors.WithStack2(entry.factory(ctx, config, scheduler.WithQueuePrefix(name), cache, mux, vars))
if entry, ok := r.registry[name]; ok {
return errors.WithStack2(entry.factory(ctx, config, cache, mux, vars))
}
return nil, errors.Errorf("%s: %w", name, ErrNotFound)
}
Expand Down
7 changes: 3 additions & 4 deletions internal/strategy/apiv1.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,11 @@ import (
"time"

"github.com/block/cachew/internal/cache"
"github.com/block/cachew/internal/jobscheduler"
"github.com/block/cachew/internal/logging"
)

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

var _ Strategy = (*APIV1)(nil)
Expand All @@ -28,7 +27,7 @@ type APIV1 struct {
logger *slog.Logger
}

func NewAPIV1(ctx context.Context, _ struct{}, _ jobscheduler.Scheduler, cache cache.Cache, mux Mux) (*APIV1, error) {
func NewAPIV1(ctx context.Context, _ struct{}, cache cache.Cache, mux Mux) (*APIV1, error) {
s := &APIV1{
logger: logging.FromContext(ctx),
cache: cache,
Expand Down
7 changes: 3 additions & 4 deletions internal/strategy/artifactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ import (
"strings"

"github.com/block/cachew/internal/cache"
"github.com/block/cachew/internal/jobscheduler"
"github.com/block/cachew/internal/logging"
"github.com/block/cachew/internal/strategy/handler"
)

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

// ArtifactoryConfig represents the configuration for the Artifactory strategy.
Expand Down Expand Up @@ -53,7 +52,7 @@ type Artifactory struct {

var _ Strategy = (*Artifactory)(nil)

func NewArtifactory(ctx context.Context, config ArtifactoryConfig, _ jobscheduler.Scheduler, cache cache.Cache, mux Mux) (*Artifactory, error) {
func NewArtifactory(ctx context.Context, config ArtifactoryConfig, cache cache.Cache, mux Mux) (*Artifactory, error) {
u, err := url.Parse(config.Target)
if err != nil {
return nil, fmt.Errorf("invalid target URL: %w", err)
Expand Down
7 changes: 3 additions & 4 deletions internal/strategy/artifactory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/alecthomas/assert/v2"

"github.com/block/cachew/internal/cache"
"github.com/block/cachew/internal/jobscheduler"
"github.com/block/cachew/internal/logging"
"github.com/block/cachew/internal/strategy"
)
Expand Down Expand Up @@ -64,7 +63,7 @@ func setupArtifactoryTest(t *testing.T, config strategy.ArtifactoryConfig) (*moc
t.Cleanup(func() { memCache.Close() })

mux := http.NewServeMux()
_, err = strategy.NewArtifactory(ctx, config, jobscheduler.New(ctx, jobscheduler.Config{}), memCache, mux)
_, err = strategy.NewArtifactory(ctx, config, memCache, mux)
assert.NoError(t, err)

return mock, mux, ctx
Expand Down Expand Up @@ -213,7 +212,7 @@ func TestArtifactoryString(t *testing.T) {
mux := http.NewServeMux()
artifactory, err := strategy.NewArtifactory(ctx, strategy.ArtifactoryConfig{
Target: "https://ec2.example.jfrog.io",
}, jobscheduler.New(ctx, jobscheduler.Config{}), memCache, mux)
}, memCache, mux)
assert.NoError(t, err)

assert.Equal(t, "artifactory:ec2.example.jfrog.io", artifactory.String())
Expand All @@ -228,7 +227,7 @@ func TestArtifactoryInvalidTargetURL(t *testing.T) {
mux := http.NewServeMux()
_, err = strategy.NewArtifactory(ctx, strategy.ArtifactoryConfig{
Target: "://invalid-url",
}, jobscheduler.New(ctx, jobscheduler.Config{}), memCache, mux)
}, memCache, mux)
assert.Error(t, err)
}

Expand Down
6 changes: 4 additions & 2 deletions internal/strategy/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ import (
"github.com/block/cachew/internal/strategy"
)

func init() {
strategy.Register("git", "Caches Git repositories, including bundle and tarball snapshots.", New)
func Register(r *strategy.Registry, scheduler jobscheduler.Scheduler) {
strategy.Register(r, "git", "Caches Git repositories, including bundle and tarball snapshots.", func(ctx context.Context, config Config, cache cache.Cache, mux strategy.Mux) (*Strategy, error) {
return New(ctx, config, scheduler, cache, mux)
})
}

type Config struct {
Expand Down
7 changes: 3 additions & 4 deletions internal/strategy/github_releases.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,12 @@ import (

"github.com/block/cachew/internal/cache"
"github.com/block/cachew/internal/httputil"
"github.com/block/cachew/internal/jobscheduler"
"github.com/block/cachew/internal/logging"
"github.com/block/cachew/internal/strategy/handler"
)

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

type GitHubReleasesConfig struct {
Expand All @@ -34,7 +33,7 @@ type GitHubReleases struct {
}

// NewGitHubReleases creates a [Strategy] that fetches private (and public) release binaries from GitHub.
func NewGitHubReleases(ctx context.Context, config GitHubReleasesConfig, _ jobscheduler.Scheduler, cache cache.Cache, mux Mux) (*GitHubReleases, error) {
func NewGitHubReleases(ctx context.Context, config GitHubReleasesConfig, cache cache.Cache, mux Mux) (*GitHubReleases, error) {
s := &GitHubReleases{
config: config,
cache: cache,
Expand Down
7 changes: 3 additions & 4 deletions internal/strategy/github_releases_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/alecthomas/assert/v2"

"github.com/block/cachew/internal/cache"
"github.com/block/cachew/internal/jobscheduler"
"github.com/block/cachew/internal/logging"
"github.com/block/cachew/internal/strategy"
)
Expand Down Expand Up @@ -127,7 +126,7 @@ func setupTest(t *testing.T, config strategy.GitHubReleasesConfig) (*mockGitHubS
t.Cleanup(func() { memCache.Close() })

mux := http.NewServeMux()
_, err = strategy.NewGitHubReleases(ctx, config, jobscheduler.New(ctx, jobscheduler.Config{}), memCache, mux)
_, err = strategy.NewGitHubReleases(ctx, config, memCache, mux)
assert.NoError(t, err)

return mock, mux, ctx
Expand Down Expand Up @@ -242,7 +241,7 @@ func TestGitHubReleasesNoToken(t *testing.T) {
defer memCache.Close()

mux := http.NewServeMux()
gh, err := strategy.NewGitHubReleases(ctx, strategy.GitHubReleasesConfig{}, jobscheduler.New(ctx, jobscheduler.Config{}), memCache, mux)
gh, err := strategy.NewGitHubReleases(ctx, strategy.GitHubReleasesConfig{}, memCache, mux)
assert.NoError(t, err)
assert.Equal(t, "github-releases", gh.String())
}
Expand All @@ -256,7 +255,7 @@ func TestGitHubReleasesString(t *testing.T) {
mux := http.NewServeMux()
gh, err := strategy.NewGitHubReleases(ctx, strategy.GitHubReleasesConfig{
Token: "test-token",
}, jobscheduler.New(ctx, jobscheduler.Config{}), memCache, mux)
}, memCache, mux)
assert.NoError(t, err)

assert.Equal(t, "github-releases", gh.String())
Expand Down
7 changes: 3 additions & 4 deletions internal/strategy/gomod/gomod.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ import (
"github.com/goproxy/goproxy"

"github.com/block/cachew/internal/cache"
"github.com/block/cachew/internal/jobscheduler"
"github.com/block/cachew/internal/logging"
"github.com/block/cachew/internal/strategy"
)

func init() {
strategy.Register("gomod", "Caches Go module proxy requests.", New)
func Register(r *strategy.Registry) {
strategy.Register(r, "gomod", "Caches Go module proxy requests.", New)
}

type Config struct {
Expand All @@ -33,7 +32,7 @@ type Strategy struct {

var _ strategy.Strategy = (*Strategy)(nil)

func New(ctx context.Context, config Config, _ jobscheduler.Scheduler, cache cache.Cache, mux strategy.Mux) (*Strategy, error) {
func New(ctx context.Context, config Config, cache cache.Cache, mux strategy.Mux) (*Strategy, error) {
parsedURL, err := url.Parse(config.Proxy)
if err != nil {
return nil, fmt.Errorf("invalid proxy URL: %w", err)
Expand Down
Loading