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
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ linters:
- depguard # checks if package imports are in a list of acceptable packages
- dupl # tool for code clone detection
- durationcheck # checks for two durations multiplied together
- embeddedstructfieldcheck # checks embedded types in structs
# - embeddedstructfieldcheck # checks embedded types in structs
- errcheck # checking for unchecked errors, these unchecked errors can be critical bugs in some cases
- errname # checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error
- errorlint # finds code that will cause problems with the error wrapping scheme introduced in Go 1.13
Expand Down
1 change: 1 addition & 0 deletions cachew.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# target = "https://example.jfrog.io"
# }

url = "http://127.0.0.1:8080"

git {
mirror-root = "./state/git-mirrors"
Expand Down
30 changes: 22 additions & 8 deletions cmd/cachewd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,34 @@ import (
"github.com/block/cachew/internal/strategy/gomod"
)

type GlobalConfig struct {
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:"" hcl:"scheduler,block" prefix:"scheduler-"`
LoggingConfig logging.Config `embed:"" hcl:"logging,block" prefix:"log-"`
MetricsConfig metrics.Config `embed:"" hcl:"metrics,block" prefix:"metrics-"`
}

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-"`
Config *os.File `hcl:"-" help:"Configuration file path." placeholder:"PATH" required:"" default:"cachew.hcl"`

// GlobalConfig accepts command-line, but can also be parsed from HCL.
GlobalConfig
}

func main() {
kctx := kong.Parse(&cli, kong.DefaultEnvars("CACHEW"))

ast, err := hcl.Parse(cli.Config)
kctx.FatalIfErrorf(err)

globalConfig, providersConfig := config.Split[GlobalConfig](ast)

err = hcl.UnmarshalAST(globalConfig, &cli.GlobalConfig)
kctx.FatalIfErrorf(err)

ctx := context.Background()
logger, ctx := logging.Configure(ctx, cli.LoggingConfig)

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

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

metricsClient, err := metrics.New(ctx, cli.MetricsConfig)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/block/cachew
go 1.25.5

require (
github.com/alecthomas/hcl/v2 v2.4.0
github.com/alecthomas/hcl/v2 v2.5.0
github.com/alecthomas/kong v1.13.0
github.com/goproxy/goproxy v0.25.0
github.com/lmittmann/tint v1.1.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ github.com/alecthomas/chroma/v2 v2.23.1 h1:nv2AVZdTyClGbVQkIzlDm/rnhk1E9bU9nXwmZ
github.com/alecthomas/chroma/v2 v2.23.1/go.mod h1:NqVhfBR0lte5Ouh3DcthuUCTUpDC9cxBOfyMbMQPs3o=
github.com/alecthomas/errors v0.9.1 h1:JNXtU30rtMNARCkW41OTZ4yL6Lyocq20xIJgIw2raqI=
github.com/alecthomas/errors v0.9.1/go.mod h1:l8mjMEHMGUdIWPMNtvDyRYPVS1fQFXHFXc/iVCCLGkI=
github.com/alecthomas/hcl/v2 v2.4.0 h1:j7sPnff/f6FLAPTZmpFzHS2ENwE/dHj6K40bRb9nk4g=
github.com/alecthomas/hcl/v2 v2.4.0/go.mod h1:4UUp66q8ony5j8tm2bANErujUpZ3GgHBLgaKxTUQlQI=
github.com/alecthomas/hcl/v2 v2.5.0 h1:0L0oGrZPHokiXaKtsEcLa3hBjfVrRLUUK3u5vXQSybg=
github.com/alecthomas/hcl/v2 v2.5.0/go.mod h1:4UUp66q8ony5j8tm2bANErujUpZ3GgHBLgaKxTUQlQI=
github.com/alecthomas/kong v1.13.0 h1:5e/7XC3ugvhP1DQBmTS+WuHtCbcv44hsohMgcvVxSrA=
github.com/alecthomas/kong v1.13.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U=
Expand Down
5 changes: 5 additions & 0 deletions internal/cache/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ func (r *Registry) Schema() *hcl.AST {
return ast
}

func (r *Registry) Exists(name string) bool {
_, ok := r.registry[name]
return ok
}

// Create a new cache instance from the given name and configuration.
//
// Will return "ErrNotFound" if the cache backend is not found.
Expand Down
56 changes: 46 additions & 10 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package config

import (
"context"
"io"
"log/slog"
"net/http"
"os"
Expand Down Expand Up @@ -36,27 +35,64 @@ 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, sr *strategy.Registry) *hcl.AST {
func Schema[GlobalConfig any](cr *cache.Registry, sr *strategy.Registry) *hcl.AST {
globalSchema, err := hcl.Schema(new(GlobalConfig))
if err != nil {
panic(err)
}
return &hcl.AST{
Entries: append(sr.Schema().Entries, cr.Schema().Entries...),
Entries: append(globalSchema.Entries, append(sr.Schema().Entries, cr.Schema().Entries...)...),
}
}

// Split configuration into global config and provider-specific config.
//
// At this point we don't know what config the providers require, so we just pull out the global config and assume
// everything else is for the providers.
func Split[GlobalConfig any](ast *hcl.AST) (global, providers *hcl.AST) {
globalSchema, err := hcl.Schema(new(GlobalConfig))
if err != nil {
panic(err)
}

globals := map[string]bool{}
for _, entry := range globalSchema.Entries {
switch entry.(type) {
case *hcl.Attribute, *hcl.Block:
globals[entry.EntryKey()] = true
}
}

global = &hcl.AST{Pos: ast.Pos}
providers = &hcl.AST{Pos: ast.Pos}

for _, node := range ast.Entries {
switch node := node.(type) {
case *hcl.Block:
if globals[node.Name] {
global.Entries = append(global.Entries, node.Body...)
} else {
providers.Entries = append(providers.Entries, node)
}

case *hcl.Attribute: // Attributes are always for the global config
global.Entries = append(global.Entries, node)
}
}

return global, providers
}

// Load HCL configuration and uses that to construct the cache backend, and proxy strategies.
// Load HCL configuration and use that to construct the cache backend, and proxy strategies.
func Load(
ctx context.Context,
cr *cache.Registry,
sr *strategy.Registry,
r io.Reader,
ast *hcl.AST,
mux *http.ServeMux,
vars map[string]string,
) error {
logger := logging.FromContext(ctx)
ast, err := hcl.Parse(r)
if err != nil {
return errors.WithStack(err)
}

expandVars(ast, vars)

strategyCandidates := []*hcl.Block{
Expand Down
5 changes: 5 additions & 0 deletions internal/strategy/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ func (r *Registry) Schema() *hcl.AST {
return ast
}

func (r *Registry) Exists(name string) bool {
_, ok := r.registry[name]
return ok
}

// Create a new proxy strategy.
//
// Will return "ErrNotFound" if the strategy is not found.
Expand Down